/** * Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com> */ package org.deephacks.confit.internal.core.property.typesafe.impl; import java.io.ObjectStreamException; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import org.deephacks.confit.internal.core.property.typesafe.ConfigList; import org.deephacks.confit.internal.core.property.typesafe.ConfigRenderOptions; import org.deephacks.confit.internal.core.property.typesafe.ConfigValueType; import org.deephacks.confit.internal.core.property.typesafe.ConfigException; import org.deephacks.confit.internal.core.property.typesafe.ConfigOrigin; import org.deephacks.confit.internal.core.property.typesafe.ConfigValue; final class SimpleConfigList extends AbstractConfigValue implements ConfigList, Serializable { private static final long serialVersionUID = 2L; final private List<AbstractConfigValue> value; final private boolean resolved; SimpleConfigList(ConfigOrigin origin, List<AbstractConfigValue> value) { this(origin, value, ResolveStatus .fromValues(value)); } SimpleConfigList(ConfigOrigin origin, List<AbstractConfigValue> value, ResolveStatus status) { super(origin); this.value = value; this.resolved = status == ResolveStatus.RESOLVED; // kind of an expensive debug check (makes this constructor pointless) if (status != ResolveStatus.fromValues(value)) throw new ConfigException.BugOrBroken( "SimpleConfigList created with wrong resolve status: " + this); } @Override public ConfigValueType valueType() { return ConfigValueType.LIST; } @Override public List<Object> unwrapped() { List<Object> list = new ArrayList<Object>(); for (AbstractConfigValue v : value) { list.add(v.unwrapped()); } return list; } @Override ResolveStatus resolveStatus() { return ResolveStatus.fromBoolean(resolved); } private SimpleConfigList modify(NoExceptionsModifier modifier, ResolveStatus newResolveStatus) { try { return modifyMayThrow(modifier, newResolveStatus); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new ConfigException.BugOrBroken("unexpected checked exception", e); } } private SimpleConfigList modifyMayThrow(Modifier modifier, ResolveStatus newResolveStatus) throws Exception { // lazy-create for optimization List<AbstractConfigValue> changed = null; int i = 0; for (AbstractConfigValue v : value) { AbstractConfigValue modified = modifier.modifyChildMayThrow(null /* key */, v); // lazy-create the new list if required if (changed == null && modified != v) { changed = new ArrayList<AbstractConfigValue>(); for (int j = 0; j < i; ++j) { changed.add(value.get(j)); } } // once the new list is created, list elements // have to go in it. if modifyChild returned // null, we drop that element. if (changed != null && modified != null) { changed.add(modified); } i += 1; } if (changed != null) { return new SimpleConfigList(origin(), changed, newResolveStatus); } else { return this; } } @Override SimpleConfigList resolveSubstitutions(final ResolveContext context) throws NotPossibleToResolve { if (resolved) return this; if (context.isRestrictedToChild()) { // if a list restricts to a child path, then it has no child paths, // so nothing to do. return this; } else { try { return modifyMayThrow(new Modifier() { @Override public AbstractConfigValue modifyChildMayThrow(String key, AbstractConfigValue v) throws NotPossibleToResolve { return context.resolve(v); } }, ResolveStatus.RESOLVED); } catch (NotPossibleToResolve e) { throw e; } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new ConfigException.BugOrBroken("unexpected checked exception", e); } } } @Override SimpleConfigList relativized(final Path prefix) { return modify(new NoExceptionsModifier() { @Override public AbstractConfigValue modifyChild(String key, AbstractConfigValue v) { return v.relativized(prefix); } }, resolveStatus()); } @Override protected boolean canEqual(Object other) { return other instanceof SimpleConfigList; } @Override public boolean equals(Object other) { // note that "origin" is deliberately NOT part of equality if (other instanceof SimpleConfigList) { // optimization to avoid unwrapped() for two ConfigList return canEqual(other) && value.equals(((SimpleConfigList) other).value); } else { return false; } } @Override public int hashCode() { // note that "origin" is deliberately NOT part of equality return value.hashCode(); } @Override protected void render(StringBuilder sb, int indent, ConfigRenderOptions options) { if (value.isEmpty()) { sb.append("[]"); } else { sb.append("["); if (options.getFormatted()) sb.append('\n'); for (AbstractConfigValue v : value) { if (options.getOriginComments()) { indent(sb, indent + 1, options); sb.append("# "); sb.append(v.origin().description()); sb.append("\n"); } if (options.getComments()) { for (String comment : v.origin().comments()) { indent(sb, indent + 1, options); sb.append("# "); sb.append(comment); sb.append("\n"); } } indent(sb, indent + 1, options); v.render(sb, indent + 1, options); sb.append(","); if (options.getFormatted()) sb.append('\n'); } sb.setLength(sb.length() - 1); // chop or newline if (options.getFormatted()) { sb.setLength(sb.length() - 1); // also chop comma sb.append('\n'); indent(sb, indent, options); } sb.append("]"); } } @Override public boolean contains(Object o) { return value.contains(o); } @Override public boolean containsAll(Collection<?> c) { return value.containsAll(c); } @Override public AbstractConfigValue get(int index) { return value.get(index); } @Override public int indexOf(Object o) { return value.indexOf(o); } @Override public boolean isEmpty() { return value.isEmpty(); } @Override public Iterator<ConfigValue> iterator() { final Iterator<AbstractConfigValue> i = value.iterator(); return new Iterator<ConfigValue>() { @Override public boolean hasNext() { return i.hasNext(); } @Override public ConfigValue next() { return i.next(); } @Override public void remove() { throw weAreImmutable("iterator().remove"); } }; } @Override public int lastIndexOf(Object o) { return value.lastIndexOf(o); } private static ListIterator<ConfigValue> wrapListIterator( final ListIterator<AbstractConfigValue> i) { return new ListIterator<ConfigValue>() { @Override public boolean hasNext() { return i.hasNext(); } @Override public ConfigValue next() { return i.next(); } @Override public void remove() { throw weAreImmutable("listIterator().remove"); } @Override public void add(ConfigValue arg0) { throw weAreImmutable("listIterator().add"); } @Override public boolean hasPrevious() { return i.hasPrevious(); } @Override public int nextIndex() { return i.nextIndex(); } @Override public ConfigValue previous() { return i.previous(); } @Override public int previousIndex() { return i.previousIndex(); } @Override public void set(ConfigValue arg0) { throw weAreImmutable("listIterator().set"); } }; } @Override public ListIterator<ConfigValue> listIterator() { return wrapListIterator(value.listIterator()); } @Override public ListIterator<ConfigValue> listIterator(int index) { return wrapListIterator(value.listIterator(index)); } @Override public int size() { return value.size(); } @Override public List<ConfigValue> subList(int fromIndex, int toIndex) { List<ConfigValue> list = new ArrayList<ConfigValue>(); // yay bloat caused by lack of type variance for (AbstractConfigValue v : value.subList(fromIndex, toIndex)) { list.add(v); } return list; } @Override public Object[] toArray() { return value.toArray(); } @Override public <T> T[] toArray(T[] a) { return value.toArray(a); } private static UnsupportedOperationException weAreImmutable(String method) { return new UnsupportedOperationException( "ConfigList is immutable, you can't call List.'" + method + "'"); } @Override public boolean add(ConfigValue e) { throw weAreImmutable("add"); } @Override public void add(int index, ConfigValue element) { throw weAreImmutable("add"); } @Override public boolean addAll(Collection<? extends ConfigValue> c) { throw weAreImmutable("addAll"); } @Override public boolean addAll(int index, Collection<? extends ConfigValue> c) { throw weAreImmutable("addAll"); } @Override public void clear() { throw weAreImmutable("clear"); } @Override public boolean remove(Object o) { throw weAreImmutable("remove"); } @Override public ConfigValue remove(int index) { throw weAreImmutable("remove"); } @Override public boolean removeAll(Collection<?> c) { throw weAreImmutable("removeAll"); } @Override public boolean retainAll(Collection<?> c) { throw weAreImmutable("retainAll"); } @Override public ConfigValue set(int index, ConfigValue element) { throw weAreImmutable("set"); } @Override protected SimpleConfigList newCopy(ConfigOrigin newOrigin) { return new SimpleConfigList(newOrigin, value); } final SimpleConfigList concatenate(SimpleConfigList other) { ConfigOrigin combinedOrigin = SimpleConfigOrigin.mergeOrigins(origin(), other.origin()); List<AbstractConfigValue> combined = new ArrayList<AbstractConfigValue>(value.size() + other.value.size()); combined.addAll(value); combined.addAll(other.value); return new SimpleConfigList(combinedOrigin, combined); } // serialization list goes through SerializedConfigValue private Object writeReplace() throws ObjectStreamException { return new SerializedConfigValue(this); } }