/* * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.runtime.core.api.locator; import static java.lang.Integer.parseInt; import static org.apache.commons.lang.StringUtils.join; import static org.mule.runtime.api.util.Preconditions.checkArgument; import static org.mule.runtime.api.util.Preconditions.checkState; import static org.mule.runtime.core.api.locator.Location.LocationImpl.PARTS_SEPARATOR; import java.util.LinkedList; import java.util.List; /** * A location allows to define the position of a certain component in the configuration. * * Only components contained within global components with name can be referenced using a {@link Location} instance. * * It is expected that the string representation of the object defined the serialized for of the location which consist of the * global element name and the parts separated by an slash character. * * @since 4.0 */ public interface Location { /** * @return the global component name that contains the referenced component. */ String getGlobalComponentName(); /** * @return the parts within the global component that define the location of the component. */ List<String> getParts(); /** * @return a new builder instance. */ static Builder builder() { return new LocationBuilder(); } /** * A builder to create a {@link Location} object. * * All {@link Location} instances must be created using this builder. The builder implementation may not be thread safe but it * is immutable so each method call in the builder returns a new instance so it can be reused. * * @since 4.0 */ interface Builder { /** * Sets the name of the global component. This method must only be called once. * * @param globalName the name of the global component * @return a new builder with the provided configuration. */ Builder globalName(String globalName); /** * Adds a new part at the end of the location. * * @param part the name of the part * @return a new builder with the provided configuration. */ Builder addPart(String part); /** * Adds a new "processors" part at the end of the location. * * All component that allow nested processors must have the "processors" as attribute for holding the nested processors. * * @return a new builder with the provided configuration. */ Builder addProcessorsPart(); /** * Adds a new index part. The index part is used to reference a component within a collection. * * There cannot be two index parts consecutively. * * @param index the index of the component. * @return a new builder with the provided configuration. */ Builder addIndexPart(int index); /** * @return a location build with the provided configuration. */ Location build(); } static class LocationImpl implements Location { protected static final String PARTS_SEPARATOR = "/"; private LinkedList<String> parts = new LinkedList<>(); @Override public String getGlobalComponentName() { return parts.get(0); } @Override public List<String> getParts() { return parts.subList(1, parts.size() - 1); } @Override public String toString() { return join(parts, PARTS_SEPARATOR); } } static class LocationBuilder implements Builder { private LocationImpl location = new LocationImpl(); private boolean globalNameAlreadySet = false; @Override public Builder globalName(String globalName) { globalNameAlreadySet = true; verifyPartDoesNotContainsSlash(globalName); LocationBuilder locationBuilder = builderCopy(); locationBuilder.location.parts.add(0, globalName); return locationBuilder; } @Override public Builder addPart(String part) { verifyPartDoesNotContainsSlash(part); LocationBuilder locationBuilder = builderCopy(); locationBuilder.location.parts.addLast(part); return locationBuilder; } @Override public Builder addProcessorsPart() { LocationBuilder locationBuilder = builderCopy(); locationBuilder.location.parts.add("processors"); return locationBuilder; } @Override public Builder addIndexPart(int index) { verifyPreviousPartIsNotIndex(); LocationBuilder locationBuilder = builderCopy(); locationBuilder.location.parts.addLast(String.valueOf(index)); return locationBuilder; } private void verifyPreviousPartIsNotIndex() { checkState(!location.parts.isEmpty(), "An index cannot be the first part"); try { parseInt(location.parts.getLast()); checkState(false, "A location cannot have two consecutive index"); } catch (NumberFormatException e) { // all good, not an index. } } private LocationBuilder builderCopy() { LocationBuilder locationBuilder = new LocationBuilder(); locationBuilder.globalNameAlreadySet = this.globalNameAlreadySet; locationBuilder.location.parts.addAll(this.location.parts); return locationBuilder; } private void verifyPartDoesNotContainsSlash(String part) { checkArgument(!part.contains(PARTS_SEPARATOR), "Slash cannot be part of the global name or part, bad part is " + part); } @Override public Location build() { checkState(globalNameAlreadySet, "global component name must be set"); return location; } } }