package org.netbeans.gradle.project.api.config; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import javax.annotation.Nonnull; import org.jtrim.collections.ArraysEx; import org.jtrim.utils.ExceptionHelper; /** * Defines a path to a subtree in a configuration tree. For most practical * purposes, you can consider keys in this path as nested {@literal XML} tags. * The keys in the path are case-sensitive. * <P> * Instances of this class are immutable and as such can be shared without any * further synchronization. * <P> * Instances of this class can be created through one of its factory methods. * * @see ConfigTree */ public final class ConfigPath { private static final String[] NO_KEYS = new String[0]; public static final ConfigPath ROOT = new ConfigPath(NO_KEYS, Collections.<String>emptyList()); private final String[] keys; private final List<String> keysAsList; // True correctness depends on this variable not being explicitly initialized. private int hashCache; private ConfigPath(String[] keys) { this(keys, ArraysEx.viewAsList(keys)); ExceptionHelper.checkNotNullElements(this.keys, "keys"); } private ConfigPath(String[] keys, List<String> keysAsList) { this.keys = keys; this.keysAsList = keysAsList; } /** * Returns a {@code ConfigPath} with the given keys in the path. * * @param keys the keys pointing to a subtree in a configuration tree. * This argument cannot be {@code null} and the keys cannot be {@code null}. * @return a {@code ConfigPath} with the given keys in the path. This * method never returns {@code null}. */ @Nonnull public static ConfigPath fromKeys(@Nonnull String... keys) { return keys.length > 0 ? new ConfigPath(keys.clone()) : ROOT; } /** * Returns a {@code ConfigPath} with the given keys in the path. * * @param keys the keys pointing to a subtree in a configuration tree. * This argument cannot be {@code null} and the keys cannot be {@code null}. * @return a {@code ConfigPath} with the given keys in the path. This * method never returns {@code null}. */ @Nonnull public static ConfigPath fromKeys(@Nonnull List<String> keys) { return keys.isEmpty() ? ROOT : new ConfigPath(keys.toArray(NO_KEYS)); } /** * Returns a {@code ConfigPath} which has the starts with the given keys * and ends with the keys of this {@code ConfigPath}. * * @param parentKeys the keys to be prepended to the keys of this * {@code ConfigPath}. This argument cannot be {@code null} and no keys * can be {@code null}. * @return a {@code ConfigPath} which has the starts with the given keys * and ends with the keys of this {@code ConfigPath}. This method never * returns {@code null}. */ @Nonnull public ConfigPath withParentPath(@Nonnull String... parentKeys) { List<String> newKeys = new ArrayList<>(keys.length + parentKeys.length); newKeys.addAll(Arrays.asList(parentKeys)); newKeys.addAll(keysAsList); return ConfigPath.fromKeys(newKeys); } /** * Returns a {@code ConfigPath} which has the starts with the keys of this * {@code ConfigPath} and ends with the given keys. * * @param childKeys the keys to be appended to the keys of this * {@code ConfigPath}. This argument cannot be {@code null} and no keys * can be {@code null}. * @return a {@code ConfigPath} which has the starts with the keys of this * {@code ConfigPath} and ends with the given keys. This method never * returns {@code null}. */ @Nonnull public ConfigPath withChildPath(@Nonnull String... childKeys) { List<String> newKeys = new ArrayList<>(keys.length + childKeys.length); newKeys.addAll(keysAsList); newKeys.addAll(Arrays.asList(childKeys)); return ConfigPath.fromKeys(newKeys); } /** * Returns the number of keys found in this {@code ConfigPath}. That is, * the same number as {@code getKeys().size()}. * * @return the number of keys found in this {@code ConfigPath}. This method * always returns a value greater than or equal to zero. */ public int getKeyCount() { return keys.length; } /** * Returns the key at the given index in the {@link #getKeys() keys list} * of this {@code ConfigPath}. * * @param index the index of the requested key. This index must be greater * or equal to zero and less than {@link #getKeyCount() getKeyCount()}. * @return the key at the given index in the {@link #getKeys() keys list} * of this {@code ConfigPath}. This method never returns {@code null}. */ @Nonnull public String getKeyAt(int index) { return keys[index]; } /** * Returns the keys of this {@code ConfigPath} defining the edges in a * {@link ConfigTree}. * * @return the keys of this {@code ConfigPath} defining the edges in a * {@link ConfigTree}. This method never returns {@code null} and none * of the keys are {@code null} as well. */ @Nonnull public List<String> getKeys() { return keysAsList; } /** * Returns {@code true} if the given {@code ConfigPath} is the child path * of this {@code ConfigPath}, {@code false} otherwise. That is, if the keys * of the given {@code ConfigPath} starts with the keys of this * {@code ConfigPath}. If the given {@code ConfigPath} specifies the same * path as this {@code ConfigPath}, this method returns {@code true}. * * @param childCandidate the {@code ConfigPath} to be tested if it is a * child path of this {@code ConfigPath}. This argument cannot be * {@code null}. * @return {@code true} if the given {@code ConfigPath} is the child path * of this {@code ConfigPath}, {@code false} otherwise */ public boolean isParentOfOrEqual(@Nonnull ConfigPath childCandidate) { String[] childCandidateKeys = childCandidate.keys; if (childCandidateKeys.length < keys.length) { return false; } for (int i = 0; i < keys.length; i++) { if (!Objects.equals(keys[i], childCandidateKeys[i])) { return false; } } return true; } /** * Returns a hash code compatible with the {@code equals} method. * * @return a hash code compatible with the {@code equals} method */ @Override public int hashCode() { int hash = hashCache; if (hash == 0) { hash = 5; hash = 61 * hash + Arrays.hashCode(keys); hashCache = hash; } return hash; } /** * Returns {@code true} if the passed object is a {@code ConfigPath} and * it has the same {@link #getKeys() keys} as this path, {@code false} * otherwise. * * @param obj the object to be tested for equality. This argument might * be {@code null}, in which case the return value is {@code false}. * @return {@code true} if the passed object is a {@code ConfigPath} and * it has the same {@link #getKeys() keys} as this path, {@code false} * otherwise */ @Override public boolean equals(Object obj) { if (obj == null) return false; if (getClass() != obj.getClass()) return false; final ConfigPath other = (ConfigPath)obj; return Arrays.equals(this.keys, other.keys); } /** * Returns the string representation of this {@code ConfigPath} in no * particular format. The return value is intended to be used for debugging. * * @return the string representation of this {@code ConfigPath} in no * particular format. This method never returns {@code null}. */ @Override public String toString() { return Arrays.toString(keys); } }