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 /** 052 * Constructs a new instance. 053 */ 054 public OrderedProperties() { 055 // empty 056 } 057 058 @Override 059 public synchronized void clear() { 060 orderedKeys.clear(); 061 super.clear(); 062 } 063 064 @Override 065 public synchronized Object compute(final Object key, final BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) { 066 final Object compute = super.compute(key, remappingFunction); 067 if (compute != null) { 068 orderedKeys.add(key); 069 } 070 return compute; 071 } 072 073 @Override 074 public synchronized Object computeIfAbsent(final Object key, final Function<? super Object, ? extends Object> mappingFunction) { 075 final Object computeIfAbsent = super.computeIfAbsent(key, mappingFunction); 076 if (computeIfAbsent != null) { 077 orderedKeys.add(key); 078 } 079 return computeIfAbsent; 080 } 081 082 @Override 083 public Set<Map.Entry<Object, Object>> entrySet() { 084 return orderedKeys.stream().map(k -> new SimpleEntry<>(k, get(k))).collect(Collectors.toCollection(LinkedHashSet::new)); 085 } 086 087 @Override 088 public synchronized void forEach(final BiConsumer<? super Object, ? super Object> action) { 089 Objects.requireNonNull(action); 090 orderedKeys.forEach(k -> action.accept(k, get(k))); 091 } 092 093 @Override 094 public synchronized Enumeration<Object> keys() { 095 return Collections.enumeration(orderedKeys); 096 } 097 098 @Override 099 public Set<Object> keySet() { 100 return orderedKeys; 101 } 102 103 @Override 104 public synchronized Object merge(final Object key, final Object value, 105 final BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) { 106 orderedKeys.add(key); 107 return super.merge(key, value, remappingFunction); 108 } 109 110 @Override 111 public Enumeration<?> propertyNames() { 112 return Collections.enumeration(orderedKeys); 113 } 114 115 @Override 116 public synchronized Object put(final Object key, final Object value) { 117 final Object put = super.put(key, value); 118 if (put == null) { 119 orderedKeys.add(key); 120 } 121 return put; 122 } 123 124 @Override 125 public synchronized void putAll(final Map<? extends Object, ? extends Object> t) { 126 orderedKeys.addAll(t.keySet()); 127 super.putAll(t); 128 } 129 130 @Override 131 public synchronized Object putIfAbsent(final Object key, final Object value) { 132 final Object putIfAbsent = super.putIfAbsent(key, value); 133 if (putIfAbsent == null) { 134 orderedKeys.add(key); 135 } 136 return putIfAbsent; 137 } 138 139 @Override 140 public synchronized Object remove(final Object key) { 141 final Object remove = super.remove(key); 142 if (remove != null) { 143 orderedKeys.remove(key); 144 } 145 return remove; 146 } 147 148 @Override 149 public synchronized boolean remove(final Object key, final Object value) { 150 final boolean remove = super.remove(key, value); 151 if (remove) { 152 orderedKeys.remove(key); 153 } 154 return remove; 155 } 156 157 @Override 158 public synchronized String toString() { 159 // Must override for Java 17 to maintain order since the implementation is based on a map 160 final int max = size() - 1; 161 if (max == -1) { 162 return "{}"; 163 } 164 final StringBuilder sb = new StringBuilder(); 165 final Iterator<Map.Entry<Object, Object>> it = entrySet().iterator(); 166 sb.append('{'); 167 for (int i = 0;; i++) { 168 final Map.Entry<Object, Object> e = it.next(); 169 final Object key = e.getKey(); 170 final Object value = e.getValue(); 171 sb.append(key == this ? "(this Map)" : key.toString()); 172 sb.append('='); 173 sb.append(value == this ? "(this Map)" : value.toString()); 174 if (i == max) { 175 return sb.append('}').toString(); 176 } 177 sb.append(", "); 178 } 179 } 180}