/** * Copyright 2004-2016 Riccardo Solmi. All rights reserved. * This file is part of the Whole Platform. * * The Whole Platform is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The Whole Platform is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the Whole Platform. If not, see <http://www.gnu.org/licenses/>. */ package org.whole.lang.artifacts.visitors; import static org.whole.lang.artifacts.util.ArtifactsUtils.*; import java.util.Stack; import org.whole.lang.artifacts.model.FileArtifact; import org.whole.lang.artifacts.model.IArtifactsEntity; import org.whole.lang.artifacts.reflect.ArtifactsFeatureDescriptorEnum; import org.whole.lang.artifacts.util.IArtifactsOperations; import org.whole.lang.bindings.BindingManagerFactory; import org.whole.lang.bindings.IBindingManager; import org.whole.lang.codebase.IPersistenceKit; import org.whole.lang.commons.factories.CommonsEntityAdapterFactory; import org.whole.lang.commons.model.QuantifierEnum; import org.whole.lang.iterators.IEntityIterator; import org.whole.lang.iterators.IteratorFactory; import org.whole.lang.matchers.GenericMatcherFactory; import org.whole.lang.matchers.Matcher; import org.whole.lang.model.IEntity; import org.whole.lang.operations.ArtifactsGeneratorOperation; import org.whole.lang.reflect.EntityDescriptor; import org.whole.lang.reflect.EntityKinds; import org.whole.lang.util.EntityUtils; import org.whole.lang.visitors.GenericTraversalFactory; import org.whole.lang.visitors.VisitException; /** * @author Enrico Persiani */ public class ArtifactsSynchronizerVisitor<T> extends ArtifactsResourceVisitor<T> { private static final String SUB_TREE_ROOT = "subTreeRoot"; private Traverse traverse; private Synchronize synchronize; private Stack<IEntity> compareToEntityStack; private boolean isLoading; private boolean firstPass; public ArtifactsSynchronizerVisitor(IArtifactsOperations<T> artifactsOperations, T rootResource, Traverse traverse, Synchronize synchronize, boolean isLoading) { super(artifactsOperations, rootResource); this.traverse = traverse; this.synchronize = synchronize; this.compareToEntityStack = new Stack<IEntity>(); this.isLoading = isLoading; this.firstPass = !traverse.isDirectoryStructure(); } private IEntity createFilesystemModel(IArtifactsEntity model, IEntity basePath, boolean append) { T parentContext = getResource(); IEntity artifact = basePath; if (basePath != null) { parentContext = getArtifactsOperations().getParent(parentContext); if (getArtifactsOperations().getChild(parentContext, basePath) == null) throw new IllegalArgumentException("invalid root resource"); while (artifact != null && EntityUtils.isNotResolver(artifact) && !EntityUtils.isVariable(artifact)) { parentContext = getArtifactsOperations().getChild(parentContext, artifact); if (parentContext == null) break; IEntity children = getChildren(artifact); artifact = children.wIsEmpty() ? null : children.wGet(0); } } if (parentContext == null) { // cannot find path on file system if (artifact == basePath) // no file system root at all getChildren(basePath).wRemove(0); else // partial file system path artifact.wGetParent().wRemove(artifact); } else { // empty path or exists on file system IEntity fsModel = getArtifactsOperations().toArtifactsModel(parentContext); if (append && artifact != basePath) { IBindingManager bindings = BindingManagerFactory.instance.createBindingManager(); IEntityIterator<IEntity> iterator = IteratorFactory.childIterator(); iterator.reset(getChildren(fsModel)); for (IEntity child : iterator) { bindings.wDef(SUB_TREE_ROOT, child); iterator.remove(); Matcher.substitute(basePath, bindings, false); } Matcher.removeVars(basePath, true); } else basePath = fsModel; } return basePath; } private IEntity createBasePath(IArtifactsEntity entity) { IEntityIterator<IEntity> iterator = IteratorFactory.scannerIterator( IteratorFactory.ancestorIterator()) .withPattern(GenericTraversalFactory.instance.one( GenericMatcherFactory.instance.isFragmentMatcher(), GenericMatcherFactory.instance.hasKindMatcher(EntityKinds.COMPOSITE))); iterator.reset(entity); // build the ancestors path IEntity patternPath = null; if (iterator.hasNext()) { EntityDescriptor<?> ed = getChildren(entity).wGetEntityDescriptor(0); patternPath = cloneArtifact(entity, CommonsEntityAdapterFactory.createVariable( ed, SUB_TREE_ROOT, ed, QuantifierEnum.ONE_OR_MORE_GREEDY)); } while (iterator.hasNext()) patternPath = cloneArtifact(iterator.next(), patternPath); return patternPath; } private IArtifactsEntity initialize(IArtifactsEntity entity) { IEntity model; if (isLoading) { if (entity == null) { // build from scratch model = createFilesystemModel(entity, null, false); this.compareToEntityStack.push(cloneArtifact(model)); } else { IEntity basePath = createBasePath(entity); model = createFilesystemModel(entity, basePath, false); this.compareToEntityStack.push(EntityUtils.clone(entity)); } } else { IEntity basePath = createBasePath(entity); if (basePath != null) { IBindingManager bindings = BindingManagerFactory.instance.createBindingManager(); model = EntityUtils.clone(basePath); IEntityIterator<IEntity> i = IteratorFactory.childIterator(); i.reset(getChildren(entity)); for (IEntity child : i) { bindings.wDef(SUB_TREE_ROOT, EntityUtils.clone(child)); Matcher.substitute(model, bindings, false); } Matcher.removeVars(model, true); } else model = EntityUtils.clone(entity); // initialize the synchronization model IEntity filesystemModel = createFilesystemModel(entity, basePath, true); this.compareToEntityStack.push(filesystemModel); } return (IArtifactsEntity) model; } private void synchronize(IEntity children, IEntity compareToChildren) { // perform delete missing resources if (synchronize.isRemoving()) { IEntityIterator<IEntity> iterator = IteratorFactory.childIterator(); iterator.reset(compareToChildren); while (iterator.hasNext()) { IEntity child = iterator.next(); if (hasChild(children, child)) continue; if (isLoading) iterator.remove(); else getArtifactsOperations().deleteChild(getResource(), child); } } // perform remove additions if (synchronize.isUpdateOnly()) { IEntityIterator<IEntity> iterator = IteratorFactory.childIterator(); iterator.reset(children); while (iterator.hasNext()) { IEntity child = iterator.next(); if (!hasChild(compareToChildren, child)) iterator.remove(); } } } @Override protected void acceptChildren(IEntity entity) { IEntity compareToChildren = getChildren(compareToEntityStack.peek()); IEntity children = getChildren(entity); // perform synchronization synchronize(children, compareToChildren); // traverse if (!firstPass && !traverse.isDirectoryStructure()) { while (!children.wIsEmpty()) children.wRemove(0); } else { firstPass = false; IEntityIterator<IEntity> i = IteratorFactory.childIterator(); i.reset(children); for (IEntity child : i) { // first pass, remove all descendants if (!traverse.isDirectoryStructure() && !isFileArtifact(child)) { IEntity descendants = getChildren(child); while (!descendants.wIsEmpty()) descendants.wRemove(0); } if (!traverse.isFileContent() && isFileArtifact(child)) child.wRemove(ArtifactsFeatureDescriptorEnum.content); // update stacks T childContext; IEntity compareToChild = getChild(compareToChildren, child); if (compareToChild == null || (childContext = getArtifactsOperations().getChild( getResource(), compareToChild)) == null) continue; compareToEntityStack.push(compareToChild); acceptChild(child, childContext); compareToEntityStack.pop(); } } } @SuppressWarnings({ "unchecked", "rawtypes" }) public static IArtifactsEntity synchronize(IArtifactsEntity entity, Traverse traverse, Synchronize synchronize, IBindingManager bindings, IPersistenceKit defaultPersistenceKit, boolean isLoading) { Object rootResource = bindings.wGetValue("rootResource"); IArtifactsOperations<Object> artifactsOperations = (IArtifactsOperations<Object>) bindings.wGetValue("artifactsOperations"); ArtifactsSynchronizerVisitor<?> visitor = new ArtifactsSynchronizerVisitor(artifactsOperations, rootResource, traverse, synchronize, isLoading); IArtifactsEntity result = visitor.initialize(entity); result.accept(visitor); if (!isLoading) ArtifactsGeneratorOperation.generate(result, bindings); else if (traverse.isFileContent()) { Object context = artifactsOperations.getDescendant(rootResource, entity); defaultPersistenceKit = calculateInheritedPersistence(entity, defaultPersistenceKit); ArtifactsLoadFileContentsVisitor.loadContents(result, artifactsOperations, context, defaultPersistenceKit); } return result; } @Override public void visit(IArtifactsEntity entity) { throw new VisitException("unsupported entity"); } @Override public void visit(FileArtifact entity) { IEntity parent = entity.wGetParent(); if (!EntityUtils.isNull(parent) && !isLoading && !traverse.isFileContent()) parent.wRemove(entity); } public enum Traverse { SHALLOW, DEEP_DIRECTORY, DEEP_FILE, DEEP; public final boolean isDirectoryStructure() { return equals(DEEP_DIRECTORY) ||equals(Traverse.DEEP); } public final boolean isFileContent() { return equals(DEEP_FILE) || equals(DEEP); } } public enum Synchronize { REMOVE_ADD_UPDATE, ADD_UPDATE, UPDATE; public final boolean isUpdateOnly() { return equals(UPDATE); } public final boolean isRemoving() { return equals(REMOVE_ADD_UPDATE); } } }