/* * Copyright 2013 Robert von Burg <eitch@eitchnet.ch> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package li.strolch.model; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import li.strolch.exception.StrolchException; import li.strolch.utils.helper.StringHelper; /** * <p> * The {@link Locator} is an immutable object and is used to fully qualify the location of an object in the model. It * consists of a {@link List} of Strings which starting from the first value, defining the root, with the last item * defining the objects id. * </p> * * <p> * When the {@link Locator} is formatted to a String, it resembles the same format as is used in Unix based files * systems, with slashes (/), separating the different list values * </p> * * <p> * A {@link Locator} is always immutable, modifications return a new instance * </p> * * @author Robert von Burg <eitch@eitchnet.ch> */ public class Locator { /** * The separator used when formatting a {@link Locator} object ot a string */ public static final String PATH_SEPARATOR = "/"; //$NON-NLS-1$ /** * {@link List} of path elements, with the first being the top level or root element */ private final List<String> pathElements; /** * Constructs a new {@link Locator} with the given list of path elements * * @param pathElements * the elements making up the {@link Locator} * * @throws StrolchException * if the path is invalid, meaning has less than two elements in it */ private Locator(List<String> pathElements) throws StrolchException { if (pathElements == null || pathElements.isEmpty()) { throw new StrolchException("The path elements may not be null and must contain at least 1 item"); //$NON-NLS-1$ } this.pathElements = Collections.unmodifiableList(new ArrayList<>(pathElements)); } /** * Constructs a new {@link Locator} using the given path parts * * @param path * the path to parse for instantiate this {@link Locator} with elements * * @throws StrolchException * if the path is invalid, meaning has less than two elements in it */ private Locator(String... path) throws StrolchException { this.pathElements = Collections.unmodifiableList(Arrays.asList(path)); } /** * Constructs a new {@link Locator} by parsing the given string path. * * @param path * the path to parse for instantiate this {@link Locator} with elements * * @throws StrolchException * if the path is invalid, meaning has less than two elements in it */ private Locator(String path) throws StrolchException { this.pathElements = Collections.unmodifiableList(parsePath(path)); } /** * Internal constructor to append a sub path to a constructor * * @param path * the base path of the locator * @param subPath * the additional path */ private Locator(List<String> path, List<String> subPath) { List<String> fullPath = new ArrayList<>(); fullPath.addAll(path); fullPath.addAll(subPath); this.pathElements = Collections.unmodifiableList(fullPath); } /** * Internal constructor to append a element to a constructor * * @param path * the base path of the locator * @param element * the additional element */ private Locator(List<String> path, String element) { List<String> fullPath = new ArrayList<>(); fullPath.addAll(path); fullPath.add(element); this.pathElements = Collections.unmodifiableList(fullPath); } /** * Returns the immutable list of path elements making up this locator * * @return the pathElements */ public List<String> getPathElements() { return this.pathElements; } /** * Returns the number of elements which this {@link Locator} contains * * @return the number of elements which this {@link Locator} contains */ public int getSize() { return this.pathElements.size(); } /** * Returns a new {@link Locator} where the given sub path is appended to the locator * * @param subPathElements * the sub path to append * * @return the new locator */ public Locator append(List<String> subPathElements) { return new Locator(this.pathElements, subPathElements); } /** * Returns a new {@link Locator} where the given sub path is appended to the locator * * @param subPathElements * the sub path to append * * @return the new locator */ public Locator append(String... subPathElements) { return new Locator(this.pathElements, Arrays.asList(subPathElements)); } /** * Returns a new {@link Locator} where the given element is appended to the locator * * @param element * the element to append * * @return the new locator */ public Locator append(String element) { return new Locator(this.pathElements, element); } /** * Returns the string representation of this {@link Locator} by using the {@link #PATH_SEPARATOR} to separate the * values */ @Override public String toString() { return formatPath(this.pathElements); } /** * Parses the given path to a {@link List} of path elements by splitting the string with the {@link #PATH_SEPARATOR} * * @param path * the path to parse * * @return the list of path elements for the list * * @throws StrolchException * if the path is empty, or does not contain at least 2 elements separated by {@link #PATH_SEPARATOR} */ private List<String> parsePath(String path) throws StrolchException { if (StringHelper.isEmpty(path)) { throw new StrolchException("A path may not be empty!"); //$NON-NLS-1$ } String[] elements = path.split(Locator.PATH_SEPARATOR); return Arrays.asList(elements); } /** * Formats the given list of path elements to a String representation of the {@link Locator} * * @param pathElements * the locator elements * * @return a string representation of the path elements * * @throws StrolchException * if the path elements does not contain at least two items */ private String formatPath(List<String> pathElements) throws StrolchException { StringBuilder sb = new StringBuilder(); Iterator<String> iter = pathElements.iterator(); while (iter.hasNext()) { String element = iter.next(); sb.append(element); if (iter.hasNext()) { sb.append(Locator.PATH_SEPARATOR); } } return sb.toString(); } /** * Returns true if the given locator's path elements is the beginning of this locator's path elements * * @param locator * * @return true if the given locator's path elements is the beginning of this locator's path elements */ public boolean isEqualOrChildOf(Locator locator) { if (locator.pathElements.size() > this.pathElements.size()) return false; return this.pathElements.subList(0, locator.pathElements.size()).equals(locator.pathElements); } /** * Returns true if the given locator's path elements is the beginning of this locator's path elements, but not if * they are the same, i.e. must be an actual child * * @param locator * * @return true if the given locator's path elements is the beginning of this locator's path elements, but not if * they are the same, i.e. must be an actual child */ public boolean isChildOf(Locator locator) { if (locator.pathElements.size() >= this.pathElements.size()) return false; return this.pathElements.subList(0, locator.pathElements.size()).equals(locator.pathElements); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((this.pathElements == null) ? 0 : this.pathElements.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Locator other = (Locator) obj; if (this.pathElements == null) { if (other.pathElements != null) { return false; } } else if (!this.pathElements.equals(other.pathElements)) { return false; } return true; } /** * Instantiates a new immutable {@link Locator} instance from the given string * * @param locatorPath * the path from which to instantiate the locator * @return the immutable {@link Locator} instance */ public static Locator valueOf(String locatorPath) { return new Locator(locatorPath); } /** * Instantiates a new immutable {@link Locator} instance from the given path parts * * @param path * the path from which to instantiate the locator * @return the immutable {@link Locator} instance */ public static Locator valueOf(String... path) { return new Locator(path); } /** * Creates a new {@link LocatorBuilder} instance and appends the given elements to it * * @param path * the first element on the {@link Locator} * * @return a new {@link LocatorBuilder} instance with the given root element tag as the first element */ public static LocatorBuilder newBuilder(String... path) { return new LocatorBuilder().append(path); } /** * Creates a new {@link LocatorBuilder} instance and appends the given root element tag to it * * @param rootElement * the first element on the {@link Locator} * * @return a new {@link LocatorBuilder} instance with the given root element tag as the first element */ public static LocatorBuilder newBuilder(String rootElement) { return new LocatorBuilder().append(rootElement); } /** * {@link LocatorBuilder} is used to build {@link Locator}s where a deep hierarchy is to be created. The * {@link #append(String)} method returns itself for chain building * * @author Robert von Burg <eitch@eitchnet.ch> */ public static class LocatorBuilder { private final List<String> pathElements; /** * Default constructor */ public LocatorBuilder() { this.pathElements = new ArrayList<>(); } /** * Append the given elements to the path * * @param path * the path elements to add * * @return this instance for chaining */ public LocatorBuilder append(String... path) { for (String element : path) { this.pathElements.add(element); } return this; } /** * Append an element to the path * * @param element * the element to add * * @return this instance for chaining */ public LocatorBuilder append(String element) { this.pathElements.add(element); return this; } /** * Remove the last element from the path * * @return this instance for chaining */ public LocatorBuilder removeLast() { this.pathElements.remove(this.pathElements.size() - 1); return this; } /** * Creates an immutable {@link Locator} instance with the current elements * * @return a new {@link Locator} instance */ public Locator build() { if (this.pathElements.isEmpty()) { throw new StrolchException("The path elements must contain at least 1 item"); //$NON-NLS-1$ } return new Locator(this.pathElements); } } }