/** * Copyright 2011-2017 Asakusa Framework Team. * * 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 com.asakusafw.compiler.flow; import java.util.LinkedList; import java.util.regex.Pattern; import com.asakusafw.compiler.common.Precondition; /** * Represents a resource location. * @since 0.1.0 * @version 0.4.0 */ public class Location { /** * The suffix name for prefix locations. */ public static final String WILDCARD_SUFFIX = "-*"; //$NON-NLS-1$ private final Location parent; private final String name; private boolean prefix; /** * Creates a new instance. * @param parent the parent location, or {@code null} for the root location * @param name the resource name * @throws IllegalArgumentException if the {@code name} is {@code null} */ public Location(Location parent, String name) { Precondition.checkMustNotBeNull(name, "name"); //$NON-NLS-1$ if (parent != null && parent.isPrefix()) { throw new IllegalArgumentException(); } this.parent = parent; this.name = name; this.prefix = false; } /** * Returns a new location which this location is used as its prefix. * @return the prefixed location of this */ public Location asPrefix() { Location copy = new Location(parent, name); copy.prefix = true; return copy; } /** * Returns whether this represents a prefix location or not. * @return {@code true} if this represents a prefix location, otherwise {@code false} */ public boolean isPrefix() { return prefix; } /** * Returns the parent location. * @return the parent location, or {@code null} if there is no parent location */ public Location getParent() { return parent; } /** * Returns the name of this location. * @return the resource name */ public String getName() { return name; } /** * Returns a new location which has this as parent and the specified name as its resource name. * @param lastName the resource name * @return the created location * @throws IllegalArgumentException if the parameter is {@code null} */ public Location append(String lastName) { Precondition.checkMustNotBeNull(lastName, "lastName"); //$NON-NLS-1$ return new Location(this, lastName); } /** * Returns a new location which is concatenated this and the specified location. * @param suffix the suffix location * @return the created location * @throws IllegalArgumentException if the parameter is {@code null} */ public Location append(Location suffix) { Precondition.checkMustNotBeNull(suffix, "suffix"); //$NON-NLS-1$ LinkedList<String> segments = new LinkedList<>(); Location current = suffix; while (current != null) { segments.addFirst(current.name); current = current.parent; } current = this; for (String segment : segments) { current = new Location(current, segment); } if (suffix.isPrefix()) { current = current.asPrefix(); } return current; } /** * Parses a path string and returns it as {@link Location}. * @param pathString the path string * @param separator the separator character * @return the parsed location * @throws IllegalArgumentException if the path string is something wrong */ public static Location fromPath(String pathString, char separator) { Precondition.checkMustNotBeNull(pathString, "pathString"); //$NON-NLS-1$ boolean prefix = pathString.endsWith(WILDCARD_SUFFIX); String normalized = prefix ? pathString.substring(0, pathString.length() - WILDCARD_SUFFIX.length()) : pathString; String[] segments = normalized.split(Pattern.quote(String.valueOf(separator))); Location current = null; for (String segment : segments) { if (segment.isEmpty()) { continue; } current = new Location(current, segment); } assert current != null; if (prefix) { current = current.asPrefix(); } return current; } /** * Returns this as path string. * The separator character will not be inserted into the head of the path string. * @param separator the separator character * @return the path string */ public String toPath(char separator) { LinkedList<String> segments = new LinkedList<>(); Location current = this; while (current != null) { segments.addFirst(current.name); current = current.parent; } StringBuilder buf = new StringBuilder(); buf.append(segments.removeFirst()); for (String segment : segments) { buf.append(separator); buf.append(segment); } if (prefix) { buf.append(WILDCARD_SUFFIX); } return buf.toString(); } /** * Returns whether this location is a prefix of another location. * @param other target location * @return {@code true} if is prefix or same location, otherwise {@code false} * @throws IllegalArgumentException if some parameters were {@code null} */ public boolean isPrefixOf(Location other) { if (other == null) { throw new IllegalArgumentException("other must not be null"); //$NON-NLS-1$ } int thisSegments = count(this); int otherSegments = count(other); if (thisSegments > otherSegments) { return false; } Location current = other; for (int i = 0, n = otherSegments - thisSegments; i < n; i++) { current = current.getParent(); } return this.equals(current); } private int count(Location location) { int count = 1; Location current = location.getParent(); while (current != null) { count++; current = current.getParent(); } return count; } @Override public int hashCode() { final int prime = 31; int result = 1; Location current = this; result = prime * result + (prefix ? 0 : 1); while (current != null) { result = prime * result + current.name.hashCode(); current = current.parent; } return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Location other = (Location) obj; Location thisCur = this; Location otherCur = other; if (thisCur.prefix != otherCur.prefix) { return false; } while (thisCur != null && otherCur != null) { if (thisCur == otherCur) { return true; } if (thisCur.name.equals(otherCur.name) == false) { return false; } thisCur = thisCur.parent; otherCur = otherCur.parent; } return thisCur == otherCur; } @Override public String toString() { return toPath('/'); } }