/* * Copyright 2016 Deutsche Nationalbibliothek * * 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 org.culturegraph.mf.mangling; import java.util.ArrayDeque; import java.util.Deque; import org.culturegraph.mf.framework.helpers.DefaultStreamReceiver; /** * Tracks the <i>path</i> of the current entity. The entity path consists of the * names of all parent entities of the current entity separated by a separator * string. For example, the following sequence of events yields the path * <i>granny.mommy.me</i>: * <pre>{@literal * start-record "1" * start-entity "granny" * start-entity "mommy" * start-entity "me" * }</pre> * * <p>The current path is returned from {@link #getCurrentPath()}. * * @author Christoph Böhme * @see StreamFlattener */ public class EntityPathTracker extends DefaultStreamReceiver { public static final String DEFAULT_ENTITY_SEPARATOR = "."; private final Deque<String> entityStack = new ArrayDeque<String>(); private final StringBuilder currentPath = new StringBuilder(); private String entitySeparator = DEFAULT_ENTITY_SEPARATOR; /** * Returns the current entity path. * * @return the current entity path or an empty string if not within a record. */ public String getCurrentPath() { return currentPath.toString(); } /** * Returns the current entity path with the given literal name appended. * * @param literalName the literal name to append to the current entity path. * Must not be null. * @return the current entity path with the literal name appended. The {@link * #getEntitySeparator()} is used to separate both unless no entity was * received yet in which case only the literal name is returned. */ public String getCurrentPathWith(final String literalName) { if (entityStack.size() == 0) { return literalName; } return getCurrentPath() + entitySeparator + literalName; } /** * Returns the name of the current entity. * * @return the name of the current entity or null if not in an entity. */ public String getCurrentEntityName() { return entityStack.peek(); } public String getEntitySeparator() { return entitySeparator; } /** * Sets the separator between entity names in the path. The default separator * is "{@value DEFAULT_ENTITY_SEPARATOR}". * * <p>The separator must not be changed while processing a stream. * * @param entitySeparator the new entity separator. Can be empty to join * entity names without any separator. Multi-character * separators are also supported. Must not be null. */ public void setEntitySeparator(final String entitySeparator) { this.entitySeparator = entitySeparator; } @Override public void startRecord(final String identifier) { clearStackAndPath(); } @Override public void endRecord() { clearStackAndPath(); } @Override public void startEntity(final String name) { entityStack.push(name); appendEntityToPath(); } @Override public void endEntity() { removeEntityFromPath(); entityStack.pop(); } @Override public void closeStream() { clearStackAndPath(); } @Override public void resetStream() { clearStackAndPath(); } private void clearStackAndPath() { entityStack.clear(); currentPath.setLength(0); } private void appendEntityToPath() { if (entityStack.size() > 1) { currentPath.append(entitySeparator); } currentPath.append(entityStack.peek()); } private void removeEntityFromPath() { final String entityName = entityStack.peek(); final int oldPathLength = currentPath.length(); int lastEntityLength = entityName.length(); if (entityStack.size() > 1) { lastEntityLength += entitySeparator.length(); } currentPath.setLength(oldPathLength - lastEntityLength); } }