001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.collections4.properties; 018 019import java.util.AbstractMap.SimpleEntry; 020import java.util.Collections; 021import java.util.Enumeration; 022import java.util.Iterator; 023import java.util.LinkedHashSet; 024import java.util.Map; 025import java.util.Objects; 026import java.util.Properties; 027import java.util.Set; 028import java.util.function.BiConsumer; 029import java.util.function.BiFunction; 030import java.util.function.Function; 031import java.util.stream.Collectors; 032 033/** 034 * A drop-in replacement for {@link Properties} for ordered keys. 035 * <p> 036 * Overrides methods to keep keys in insertion order. Allows other methods in the superclass to work with ordered keys. 037 * </p> 038 * 039 * @see OrderedPropertiesFactory#INSTANCE 040 * @since 4.5.0-M1 041 */ 042public class OrderedProperties extends Properties { 043 044 private static final long serialVersionUID = 1L; 045 046 /** 047 * Preserves the insertion order. 048 */ 049 private final LinkedHashSet<Object> orderedKeys = new LinkedHashSet<>(); 050 051 @Override 052 public synchronized void clear() { 053 orderedKeys.clear(); 054 super.clear(); 055 } 056 057 @Override 058 public synchronized Object compute(final Object key, final BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) { 059 final Object compute = super.compute(key, remappingFunction); 060 if (compute != null) { 061 orderedKeys.add(key); 062 } 063 return compute; 064 } 065 066 @Override 067 public synchronized Object computeIfAbsent(final Object key, final Function<? super Object, ? extends Object> mappingFunction) { 068 final Object computeIfAbsent = super.computeIfAbsent(key, mappingFunction); 069 if (computeIfAbsent != null) { 070 orderedKeys.add(key); 071 } 072 return computeIfAbsent; 073 } 074 075 @Override 076 public Set<Map.Entry<Object, Object>> entrySet() { 077 return orderedKeys.stream().map(k -> new SimpleEntry<>(k, get(k))).collect(Collectors.toCollection(LinkedHashSet::new)); 078 } 079 080 @Override 081 public synchronized void forEach(final BiConsumer<? super Object, ? super Object> action) { 082 Objects.requireNonNull(action); 083 orderedKeys.forEach(k -> action.accept(k, get(k))); 084 } 085 086 @Override 087 public synchronized Enumeration<Object> keys() { 088 return Collections.enumeration(orderedKeys); 089 } 090 091 @Override 092 public Set<Object> keySet() { 093 return orderedKeys; 094 } 095 096 @Override 097 public synchronized Object merge(final Object key, final Object value, 098 final BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) { 099 orderedKeys.add(key); 100 return super.merge(key, value, remappingFunction); 101 } 102 103 @Override 104 public Enumeration<?> propertyNames() { 105 return Collections.enumeration(orderedKeys); 106 } 107 108 @Override 109 public synchronized Object put(final Object key, final Object value) { 110 final Object put = super.put(key, value); 111 if (put == null) { 112 orderedKeys.add(key); 113 } 114 return put; 115 } 116 117 @Override 118 public synchronized void putAll(final Map<? extends Object, ? extends Object> t) { 119 orderedKeys.addAll(t.keySet()); 120 super.putAll(t); 121 } 122 123 @Override 124 public synchronized Object putIfAbsent(final Object key, final Object value) { 125 final Object putIfAbsent = super.putIfAbsent(key, value); 126 if (putIfAbsent == null) { 127 orderedKeys.add(key); 128 } 129 return putIfAbsent; 130 } 131 132 @Override 133 public synchronized Object remove(final Object key) { 134 final Object remove = super.remove(key); 135 if (remove != null) { 136 orderedKeys.remove(key); 137 } 138 return remove; 139 } 140 141 @Override 142 public synchronized boolean remove(final Object key, final Object value) { 143 final boolean remove = super.remove(key, value); 144 if (remove) { 145 orderedKeys.remove(key); 146 } 147 return remove; 148 } 149 150 @Override 151 public synchronized String toString() { 152 // Must override for Java 17 to maintain order since the implementation is based on a map 153 final int max = size() - 1; 154 if (max == -1) { 155 return "{}"; 156 } 157 final StringBuilder sb = new StringBuilder(); 158 final Iterator<Map.Entry<Object, Object>> it = entrySet().iterator(); 159 sb.append('{'); 160 for (int i = 0;; i++) { 161 final Map.Entry<Object, Object> e = it.next(); 162 final Object key = e.getKey(); 163 final Object value = e.getValue(); 164 sb.append(key == this ? "(this Map)" : key.toString()); 165 sb.append('='); 166 sb.append(value == this ? "(this Map)" : value.toString()); 167 if (i == max) { 168 return sb.append('}').toString(); 169 } 170 sb.append(", "); 171 } 172 } 173}