/* * Copyright 2000-2009 JetBrains s.r.o. * * 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.intellij.history.core.tree; import com.intellij.history.core.Paths; import com.intellij.history.core.StreamUtil; import com.intellij.history.core.revisions.Difference; import com.intellij.history.utils.LocalHistoryLog; import com.intellij.util.io.DataInputOutputUtil; import com.intellij.util.text.CaseInsensitiveStringHashingStrategy; import gnu.trove.THashMap; import gnu.trove.TIntObjectHashMap; import org.jetbrains.annotations.NotNull; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; public class DirectoryEntry extends Entry { private final ArrayList<Entry> myChildren; public DirectoryEntry(String name) { this(toNameId(name)); } public DirectoryEntry(int nameId) { super(nameId); myChildren = new ArrayList<>(3); } public DirectoryEntry(DataInput in, @SuppressWarnings("unused") boolean dummy /* to distinguish from general constructor*/) throws IOException { super(in); int count = DataInputOutputUtil.readINT(in); myChildren = new ArrayList<>(count); while (count-- > 0) { unsafeAddChild(StreamUtil.readEntry(in)); } } @Override public void write(DataOutput out) throws IOException { super.write(out); DataInputOutputUtil.writeINT(out, myChildren.size()); for (Entry child : myChildren) { StreamUtil.writeEntry(out, child); } } @Override public long getTimestamp() { return -1; } @Override public boolean isDirectory() { return true; } @Override public void addChild(Entry child) { if (!checkDoesNotExist(child, child.getName())) return; unsafeAddChild(child); } public void addChildren(Collection<Entry> children) { myChildren.ensureCapacity(myChildren.size() + children.size()); for (Entry each : children) { unsafeAddChild(each); } } private void unsafeAddChild(Entry child) { myChildren.add(child); child.setParent(this); } protected boolean checkDoesNotExist(Entry e, String name) { Entry found = findChild(name); if (found == null) return true; if (found == e) return false; removeChild(found); LocalHistoryLog.LOG.warn(String.format("entry '%s' already exists in '%s'", name, getPath())); return true; } @Override public void removeChild(Entry child) { myChildren.remove(child); child.setParent(null); } @Override public List<Entry> getChildren() { return myChildren; } @Override public boolean hasUnavailableContent(List<Entry> entriesWithUnavailableContent) { for (Entry e : myChildren) { e.hasUnavailableContent(entriesWithUnavailableContent); } return !entriesWithUnavailableContent.isEmpty(); } @NotNull @Override public DirectoryEntry copy() { DirectoryEntry result = copyEntry(); result.myChildren.ensureCapacity(myChildren.size()); for (Entry child : myChildren) { result.unsafeAddChild(child.copy()); } return result; } protected DirectoryEntry copyEntry() { return new DirectoryEntry(getNameId()); } @Override public void collectDifferencesWith(Entry right, List<Difference> result) { DirectoryEntry e = (DirectoryEntry)right; if (!getPath().equals(e.getPath())) { result.add(new Difference(false, this, e)); } // most often we have the same children, so try processing it directly int commonIndex = 0; final int myChildrenSize = myChildren.size(); final int rightChildrenSize = e.myChildren.size(); final int minChildrenSize = Math.min(myChildrenSize, rightChildrenSize); while(commonIndex < minChildrenSize) { Entry childEntry = myChildren.get(commonIndex); Entry rightChildEntry = e.myChildren.get(commonIndex); if (childEntry.getNameId() == rightChildEntry.getNameId() && childEntry.isDirectory() == rightChildEntry.isDirectory()) { childEntry.collectDifferencesWith(rightChildEntry, result); } else { break; } ++commonIndex; } if (commonIndex == myChildrenSize && commonIndex == rightChildrenSize) return; TIntObjectHashMap<Entry> uniqueNameIdToMyChildEntries = new TIntObjectHashMap<>(myChildrenSize - commonIndex); for (int i = commonIndex; i < myChildrenSize; ++i) { Entry childEntry = myChildren.get(i); uniqueNameIdToMyChildEntries.put(childEntry.getNameId(), childEntry); } TIntObjectHashMap<Entry> uniqueNameIdToRightChildEntries = new TIntObjectHashMap<>(rightChildrenSize - commonIndex); TIntObjectHashMap<Entry> myNameIdToRightChildEntries = new TIntObjectHashMap<>(rightChildrenSize - commonIndex); for(int i = commonIndex; i < rightChildrenSize; ++i) { Entry rightChildEntry = e.myChildren.get(i); int rightChildEntryNameId = rightChildEntry.getNameId(); Entry myChildEntry = uniqueNameIdToMyChildEntries.get(rightChildEntryNameId); if (myChildEntry != null && myChildEntry.isDirectory() == rightChildEntry.isDirectory()) { uniqueNameIdToMyChildEntries.remove(rightChildEntryNameId); myNameIdToRightChildEntries.put(rightChildEntryNameId, rightChildEntry); } else { uniqueNameIdToRightChildEntries.put(rightChildEntryNameId, rightChildEntry); } } if (!Paths.isCaseSensitive() && uniqueNameIdToMyChildEntries.size() > 0 && uniqueNameIdToRightChildEntries.size() > 0) { THashMap<String, Entry> nameToEntryMap = new THashMap<>(uniqueNameIdToMyChildEntries.size(), CaseInsensitiveStringHashingStrategy.INSTANCE); uniqueNameIdToMyChildEntries.forEachValue(myChildEntry -> { nameToEntryMap.put(myChildEntry.getName(), myChildEntry); return true; }); uniqueNameIdToRightChildEntries.forEachValue(rightChildEntry -> { Entry myChildEntry = nameToEntryMap.get(rightChildEntry.getName()); if (myChildEntry != null && rightChildEntry.isDirectory() == myChildEntry.isDirectory()) { myNameIdToRightChildEntries.put(myChildEntry.getNameId(), rightChildEntry); uniqueNameIdToMyChildEntries.remove(myChildEntry.getNameId()); uniqueNameIdToRightChildEntries.remove(rightChildEntry.getNameId()); } return true; }); } for (Entry child : e.myChildren) { if (uniqueNameIdToRightChildEntries.containsKey(child.getNameId())) { child.collectCreatedDifferences(result); } } for (Entry child : myChildren) { if (uniqueNameIdToMyChildEntries.containsKey(child.getNameId())) { child.collectDeletedDifferences(result); } else { Entry itsChild = myNameIdToRightChildEntries.get(child.getNameId()); if (itsChild != null) child.collectDifferencesWith(itsChild, result); } } } Entry findDirectChild(String name, boolean isDirectory) { for (Entry child : getChildren()) { if (child.isDirectory() == isDirectory && child.nameEquals(name)) return child; } return null; } @Override protected void collectCreatedDifferences(List<Difference> result) { result.add(new Difference(false, null, this)); for (Entry child : myChildren) { child.collectCreatedDifferences(result); } } @Override protected void collectDeletedDifferences(List<Difference> result) { result.add(new Difference(false, this, null)); for (Entry child : myChildren) { child.collectDeletedDifferences(result); } } }