/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (ParsedValues.java) is part of project Time4J. * * Time4J is free software: You can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * Time4J is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Time4J. If not, see <http://www.gnu.org/licenses/>. * ----------------------------------------------------------------------- */ package net.time4j.format.expert; import net.time4j.PlainDate; import net.time4j.PlainTime; import net.time4j.engine.ChronoElement; import net.time4j.engine.ChronoException; import java.util.AbstractSet; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; /** * <p>Definiert eine aktualisierbare Wertquelle mit chronologischen Elementen, * denen beliebige Werte ohne weitere Validierung zugeordnet sind. </p> * * @author Meno Hochschild * @since 3.0 */ class ParsedValues extends ParsedEntity<ParsedValues> { //~ Statische Felder/Initialisierungen -------------------------------- private static final float LOAD_FACTOR = 0.75f; private static final int INT_PHI = 0x9E3779B9; private static final Set<ChronoElement<?>> INDEXED_ELEMENTS; static { Set<ChronoElement<?>> set = new HashSet<>(); set.add(PlainDate.YEAR); set.add(PlainDate.MONTH_AS_NUMBER); set.add(PlainDate.DAY_OF_MONTH); set.add(PlainTime.DIGITAL_HOUR_OF_DAY); set.add(PlainTime.MINUTE_OF_HOUR); set.add(PlainTime.SECOND_OF_MINUTE); set.add(PlainTime.NANO_OF_SECOND); INDEXED_ELEMENTS = Collections.unmodifiableSet(set); } //~ Instanzvariablen -------------------------------------------------- // standard mode private Object[] keys; private Object[] values; // index mode private Map<ChronoElement<?>, Object> map; private int[] ints; // index mode => date elements (year, month, day-of-month) private int len; // index mode => hour-of-day private int mask; // index mode => minute-of-hour private int threshold; // index mode => second-of-minute private int count; // index mode => nano-of-second private boolean duplicateKeysAllowed = false; private int position = -1; //~ Konstruktoren ----------------------------------------------------- /** * Standard-Konstruktor. * * @param expectedCountOfElements How many elements to be expected? * @param indexable Are only indexable elements used? */ ParsedValues( int expectedCountOfElements, boolean indexable ) { super(); if (indexable) { this.len = Integer.MIN_VALUE; this.mask = Integer.MIN_VALUE; this.threshold = Integer.MIN_VALUE; this.count = Integer.MIN_VALUE; this.keys = null; this.values = null; this.ints = new int[3]; for (int i = 0; i < 3; i++) { this.ints[i] = Integer.MIN_VALUE; } } else { this.len = arraySize(expectedCountOfElements); this.mask = this.len - 1; this.threshold = maxFill(this.len); this.keys = new Object[this.len]; this.values = null; this.ints = new int[this.len]; this.count = 0; } this.map = null; } //~ Methoden ---------------------------------------------------------- @Override public boolean contains(ChronoElement<?> element) { if (element == null) { return false; } Object[] keys = this.keys; if (keys == null) { if (element == PlainDate.YEAR) { return (this.ints[0] != Integer.MIN_VALUE); } else if (element == PlainDate.MONTH_AS_NUMBER) { return (this.ints[1] != Integer.MIN_VALUE); } else if (element == PlainDate.DAY_OF_MONTH) { return (this.ints[2] != Integer.MIN_VALUE); } else if (element == PlainTime.DIGITAL_HOUR_OF_DAY) { return (this.len != Integer.MIN_VALUE); } else if (element == PlainTime.MINUTE_OF_HOUR) { return (this.mask != Integer.MIN_VALUE); } else if (element == PlainTime.SECOND_OF_MINUTE) { return (this.threshold != Integer.MIN_VALUE); } else if (element == PlainTime.NANO_OF_SECOND) { return (this.count != Integer.MIN_VALUE); } else { Map<ChronoElement<?>, Object> m = this.map; return ((m != null) && m.containsKey(element)); } } Object current; int pos; if (((current = keys[pos = (mix(element.hashCode()) & this.mask)]) == null)) { return false; } if (element.equals(current)) { return true; } while (true) { if (((current = keys[pos = ((pos + 1) & this.mask)]) == null)) { return false; } if (element.equals(current)) { return true; } } } @Override public <V> V get(ChronoElement<V> element) { Class<V> type = element.getType(); if (type == Integer.class) { int value = this.getInt0(element); if (value == Integer.MIN_VALUE) { throw new ChronoException("No value found for: " + element.name()); } else { return type.cast(Integer.valueOf(value)); } } Object[] keys = this.keys; if (keys == null) { Map<ChronoElement<?>, Object> m = this.map; if ((m != null) && m.containsKey(element)) { return element.getType().cast(m.get(element)); } throw new ChronoException("No value found for: " + element.name()); } Object current; int pos; if ((this.values == null) || ((current = keys[pos = (mix(element.hashCode()) & this.mask)]) == null)) { throw new ChronoException("No value found for: " + element.name()); } if (element.equals(current)) { return type.cast(this.values[pos]); } while (true) { if (((current = keys[pos = ((pos + 1) & this.mask)]) == null)) { throw new ChronoException("No value found for: " + element.name()); } if (element.equals(current)) { return type.cast(this.values[pos]); } } } @Override public int getInt(ChronoElement<Integer> element) { return this.getInt0(element); } @Override public Set<ChronoElement<?>> getRegisteredElements() { if (this.keys == null) { Set<ChronoElement<?>> set = new HashSet<>(); if (this.ints[0] != Integer.MIN_VALUE) { set.add(PlainDate.YEAR); } if (this.ints[1] != Integer.MIN_VALUE) { set.add(PlainDate.MONTH_AS_NUMBER); } if (this.ints[2] != Integer.MIN_VALUE) { set.add(PlainDate.DAY_OF_MONTH); } if (this.len != Integer.MIN_VALUE) { set.add(PlainTime.DIGITAL_HOUR_OF_DAY); } if (this.mask != Integer.MIN_VALUE) { set.add(PlainTime.MINUTE_OF_HOUR); } if (this.threshold != Integer.MIN_VALUE) { set.add(PlainTime.SECOND_OF_MINUTE); } if (this.count != Integer.MIN_VALUE) { set.add(PlainTime.NANO_OF_SECOND); } if (this.map != null) { set.addAll(this.map.keySet()); } return Collections.unmodifiableSet(set); } return new KeySet(); } // used in ChronoFormatter.parseElements() void setPosition(int position) { this.position = position; } // used in ChronoFormatter.parseElements() int getPosition() { return this.position; } // disables check of ambivalent values void setNoAmbivalentCheck() { this.duplicateKeysAllowed = true; } // used by ChronoFormatter in order to determine the indexable-flag static boolean isIndexed(ChronoElement<?> element) { return INDEXED_ELEMENTS.contains(element); } // only used in ChronoFormatter.parseElements() void putAll(ParsedValues other) { if (this.keys == null) { int v = other.len; if (v != Integer.MIN_VALUE) { if ((this.len == Integer.MIN_VALUE) || this.duplicateKeysAllowed || (this.len == v)) { this.len = v; } else { throw new AmbivalentValueException(PlainTime.DIGITAL_HOUR_OF_DAY); } } v = other.mask; if (v != Integer.MIN_VALUE) { if ((this.mask == Integer.MIN_VALUE) || this.duplicateKeysAllowed || (this.mask == v)) { this.mask = v; } else { throw new AmbivalentValueException(PlainTime.MINUTE_OF_HOUR); } } v = other.threshold; if (v != Integer.MIN_VALUE) { if ((this.threshold == Integer.MIN_VALUE) || this.duplicateKeysAllowed || (this.threshold == v)) { this.threshold = v; } else { throw new AmbivalentValueException(PlainTime.SECOND_OF_MINUTE); } } v = other.count; if (v != Integer.MIN_VALUE) { if ((this.count == Integer.MIN_VALUE) || this.duplicateKeysAllowed || (this.count == v)) { this.count = v; } else { throw new AmbivalentValueException(PlainTime.NANO_OF_SECOND); } } for (int i = 0; i < 3; i++) { v = other.ints[i]; if (v != Integer.MIN_VALUE) { if ((this.ints[i] == Integer.MIN_VALUE) || this.duplicateKeysAllowed || (this.ints[i] == v)) { this.ints[i] = v; } else { throw new AmbivalentValueException(getIndexedElement(i)); } } } Map<ChronoElement<?>, Object> m = other.map; if (m != null) { for (ChronoElement<?> e : m.keySet()) { this.put(e, m.get(e)); } } return; } Object[] elements = other.keys; Object current; for (int i = 0; i < elements.length; i++) { if ((current = elements[i]) != null) { ChronoElement<?> element = ChronoElement.class.cast(current); if (element.getType() == Integer.class) { this.put(element, other.ints[i]); } else { this.put(element, other.values[i]); } } } } // called by format processors void put(ChronoElement<?> element, int v) { int pos; Object current; Object[] keys = this.keys; if (keys == null) { if (element == PlainDate.YEAR) { if (this.duplicateKeysAllowed || (this.ints[0] == Integer.MIN_VALUE) || (this.ints[0] == v)) { this.ints[0] = v; } else { throw new AmbivalentValueException(element); } } else if (element == PlainDate.MONTH_AS_NUMBER) { if (this.duplicateKeysAllowed || (this.ints[1] == Integer.MIN_VALUE) || (this.ints[1] == v)) { this.ints[1] = v; } else { throw new AmbivalentValueException(element); } } else if (element == PlainDate.DAY_OF_MONTH) { if (this.duplicateKeysAllowed || (this.ints[2] == Integer.MIN_VALUE) || (this.ints[2] == v)) { this.ints[2] = v; } else { throw new AmbivalentValueException(element); } } else if (element == PlainTime.DIGITAL_HOUR_OF_DAY) { if (this.duplicateKeysAllowed || (this.len == Integer.MIN_VALUE) || (this.len == v)) { this.len = v; } else { throw new AmbivalentValueException(element); } } else if (element == PlainTime.MINUTE_OF_HOUR) { if (this.duplicateKeysAllowed || (this.mask == Integer.MIN_VALUE) || (this.mask == v)) { this.mask = v; } else { throw new AmbivalentValueException(element); } } else if (element == PlainTime.SECOND_OF_MINUTE) { if (this.duplicateKeysAllowed || (this.threshold == Integer.MIN_VALUE) || (this.threshold == v)) { this.threshold = v; } else { throw new AmbivalentValueException(element); } } else if (element == PlainTime.NANO_OF_SECOND) { if (this.duplicateKeysAllowed || (this.count == Integer.MIN_VALUE) || (this.count == v)) { this.count = v; } else { throw new AmbivalentValueException(element); } } else { Map<ChronoElement<?>, Object> m = this.map; if (m == null) { m = new HashMap<>(); this.map = m; } Object newValue = Integer.valueOf(v); if (this.duplicateKeysAllowed || !m.containsKey(element) || newValue.equals(m.get(element))) { m.put(element, newValue); return; } else { throw new AmbivalentValueException(element); } } return; } if (!((current = keys[pos = (mix(element.hashCode()) & this.mask)]) == null)) { if (current.equals(element)) { if (this.duplicateKeysAllowed || (this.ints[pos] == v)) { this.ints[pos] = v; return; } else { throw new AmbivalentValueException(element); } } while (!((current = keys[pos = (pos + 1) & this.mask]) == null)) { if (current.equals(element)) { if (this.duplicateKeysAllowed || (this.ints[pos] == v)) { this.ints[pos] = v; return; } else { throw new AmbivalentValueException(element); } } } } keys[pos] = element; this.ints[pos] = v; if (this.count++ >= this.threshold) { rehash(arraySize(this.count)); } } // called by format processors void put(ChronoElement<?> element, Object v) { if (v == null) { this.remove(element); return; } else if (element.getType() == Integer.class) { this.put(element, Integer.class.cast(v).intValue()); return; } int pos; Object current; Object[] keys = this.keys; if (keys == null) { Map<ChronoElement<?>, Object> m = this.map; if (m == null) { m = new HashMap<>(); this.map = m; } if (this.duplicateKeysAllowed || !m.containsKey(element) || v.equals(m.get(element))) { m.put(element, v); return; } else { throw new AmbivalentValueException(element); } } if (this.values == null) { this.values = new Object[this.len]; } if (!((current = keys[pos = (mix(element.hashCode()) & this.mask)]) == null)) { if (current.equals(element)) { if (this.duplicateKeysAllowed || v.equals(this.values[pos])) { this.values[pos] = v; return; } else { throw new AmbivalentValueException(element); } } while (!((current = keys[pos = (pos + 1) & this.mask]) == null)) { if (current.equals(element)) { if (this.duplicateKeysAllowed || v.equals(this.values[pos])) { this.values[pos] = v; return; } else { throw new AmbivalentValueException(element); } } } } keys[pos] = element; this.values[pos] = v; if (this.count++ >= this.threshold) { this.rehash(arraySize(this.count)); } } @Override void setResult(Object entity) { // no-op } @Override <E> E getResult() { return null; } // called in context of erraneous or-block void reset() { if (this.keys == null) { this.len = Integer.MIN_VALUE; this.mask = Integer.MIN_VALUE; this.threshold = Integer.MIN_VALUE; this.count = Integer.MIN_VALUE; for (int i = 0; i < 3; i++) { this.ints[i] = Integer.MIN_VALUE; } this.map = null; } else { this.keys = new Object[this.keys.length]; } this.count = 0; } private int getInt0(ChronoElement<?> element) { Object[] keys = this.keys; if (keys == null) { if (element == PlainDate.YEAR) { return this.ints[0]; } else if (element == PlainDate.MONTH_AS_NUMBER) { return this.ints[1]; } else if (element == PlainDate.DAY_OF_MONTH) { return this.ints[2]; } else if (element == PlainTime.DIGITAL_HOUR_OF_DAY) { return this.len; } else if (element == PlainTime.MINUTE_OF_HOUR) { return this.mask; } else if (element == PlainTime.SECOND_OF_MINUTE) { return this.threshold; } else if (element == PlainTime.NANO_OF_SECOND) { return this.count; } Map<ChronoElement<?>, Object> m = this.map; if ((m != null) && m.containsKey(element)) { return Integer.class.cast(m.get(element)).intValue(); } return Integer.MIN_VALUE; } Object current; int pos; if (((current = keys[pos = (mix(element.hashCode()) & this.mask)]) == null)) { return Integer.MIN_VALUE; } if (element.equals(current)) { return this.ints[pos]; } while (true) { if (((current = keys[pos = ((pos + 1) & this.mask)]) == null)) { return Integer.MIN_VALUE; } if (element.equals(current)) { return this.ints[pos]; } } } private void remove(Object element) { Object[] keys = this.keys; if (keys == null) { if (element == PlainDate.YEAR) { this.ints[0] = Integer.MIN_VALUE; } else if (element == PlainDate.MONTH_AS_NUMBER) { this.ints[1] = Integer.MIN_VALUE; } else if (element == PlainDate.DAY_OF_MONTH) { this.ints[2] = Integer.MIN_VALUE; } else if (element == PlainTime.DIGITAL_HOUR_OF_DAY) { this.len = Integer.MIN_VALUE; } else if (element == PlainTime.MINUTE_OF_HOUR) { this.mask = Integer.MIN_VALUE; } else if (element == PlainTime.SECOND_OF_MINUTE) { this.threshold = Integer.MIN_VALUE; } else if (element == PlainTime.NANO_OF_SECOND) { this.count = Integer.MIN_VALUE; } else { Map<ChronoElement<?>, Object> m = this.map; if (m != null) { //noinspection SuspiciousMethodCalls m.remove(element); } } return; } Object current; int pos; if (((current = keys[pos = (mix(element.hashCode()) & this.mask)]) == null)) { return; } if (element.equals(current)) { this.removeEntry(pos); return; } while (true) { if (((current = keys[pos = ((pos + 1) & this.mask)]) == null)) { return; } if (element.equals(current)) { this.removeEntry(pos); return; } } } private void removeEntry(int pos) { this.count--; int last, slot; Object current; Object[] keys = this.keys; while (true) { pos = ((last = pos) + 1) & this.mask; while (true) { if ((current = keys[pos]) == null) { keys[last] = null; return; } slot = mix(current.hashCode()) & this.mask; if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos ) { break; } pos = (pos + 1) & this.mask; } keys[last] = current; if (this.values != null) { this.values[last] = this.values[pos]; } this.ints[last] = this.ints[pos]; } } private static int arraySize(int expectedCountOfElements) { return Math.max(2, nextPowerOfTwo((int) Math.ceil(expectedCountOfElements / LOAD_FACTOR))); } private static int nextPowerOfTwo(int x) { if (x == 0) { return 1; } x--; x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; return (x | x >> 16) + 1; } private static int maxFill(int len) { return Math.min((int) Math.ceil(len * LOAD_FACTOR), len - 1); } private static int mix(int x) { int h = x * INT_PHI; return h ^ (h >>> 16); } private void rehash(int newLen) { Object[] keys = this.keys; Object[] values = this.values; int[] ints = this.ints; int mask = newLen - 1; Object[] newKeys = new Object[newLen]; Object[] newValues = ((values == null) ? null : new Object[newLen]); int[] newInts = new int[newLen]; int i = this.len; int pos; for (int j = 0, n = this.count; j < n; j++) { // look for occupied position i while (keys[--i] == null); // look for next free position pos if (!((newKeys[pos = mix(keys[i].hashCode()) & mask]) == null)) { while (!((newKeys[pos = (pos + 1) & mask]) == null)); } // transfer data from i to pos newKeys[pos] = keys[i]; if (values != null) { newValues[pos] = values[i]; } newInts[pos] = ints[i]; } this.len = newLen; this.mask = mask; this.threshold = maxFill(newLen); this.keys = newKeys; this.values = newValues; this.ints = newInts; } private static ChronoElement<Integer> getIndexedElement(int index) { switch (index) { case 0: return PlainDate.YEAR; case 1: return PlainDate.MONTH_AS_NUMBER; case 2: return PlainDate.DAY_OF_MONTH; case 3: return PlainTime.DIGITAL_HOUR_OF_DAY; case 4: return PlainTime.MINUTE_OF_HOUR; case 5: return PlainTime.SECOND_OF_MINUTE; case 6: return PlainTime.NANO_OF_SECOND; default: throw new IllegalStateException("No element index: " + index); } } //~ Innere Klassen ---------------------------------------------------- private class KeyIterator implements Iterator<ChronoElement<?>> { //~ Instanzvariablen ---------------------------------------------- int pos = ParsedValues.this.len; int c = ParsedValues.this.count; //~ Methoden ------------------------------------------------------ @Override public boolean hasNext() { return (this.c > 0); } @Override public ChronoElement<?> next() { if (this.c > 0) { Object[] keys = ParsedValues.this.keys; while (--this.pos >= 0) { if (!(keys[this.pos] == null)) { this.c--; return ChronoElement.class.cast(keys[this.pos]); } } } throw new NoSuchElementException(); } @Override public void remove() { throw new UnsupportedOperationException("remove"); } } private class KeySet extends AbstractSet<ChronoElement<?>> { //~ Methoden ------------------------------------------------------ @Override public Iterator<ChronoElement<?>> iterator() { return new KeyIterator(); } @Override public int size() { return ParsedValues.this.count; } } }