| /* |
| * 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.changes; |
| |
| import com.intellij.history.core.Content; |
| import com.intellij.history.core.StreamUtil; |
| import com.intellij.history.utils.LocalHistoryLog; |
| import com.intellij.util.Producer; |
| import com.intellij.util.SmartList; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.io.DataInput; |
| import java.io.DataOutput; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| public class ChangeSet { |
| private final long myId; |
| @Nullable private String myName; |
| private final long myTimestamp; |
| private final List<Change> myChanges; |
| |
| private volatile boolean isLocked = false; |
| |
| public ChangeSet(long id, long timestamp) { |
| myId = id; |
| myTimestamp = timestamp; |
| myChanges = ContainerUtil.createLockFreeCopyOnWriteList(); |
| } |
| |
| public ChangeSet(DataInput in) throws IOException { |
| myId = in.readLong(); |
| myName = StreamUtil.readStringOrNull(in); |
| myTimestamp = in.readLong(); |
| |
| int count = in.readInt(); |
| List<Change> changes = new ArrayList<Change>(count); |
| while (count-- > 0) { |
| changes.add(StreamUtil.readChange(in)); |
| } |
| myChanges = Collections.unmodifiableList(changes); |
| isLocked = true; |
| } |
| |
| public void write(DataOutput out) throws IOException { |
| out.writeLong(myId); |
| StreamUtil.writeStringOrNull(out, myName); |
| out.writeLong(myTimestamp); |
| |
| out.writeInt(myChanges.size()); |
| for (Change c : myChanges) { |
| StreamUtil.writeChange(out, c); |
| } |
| } |
| |
| public void setName(@Nullable String name) { |
| myName = name; |
| } |
| |
| @Nullable |
| public String getName() { |
| return myName; |
| } |
| |
| public long getTimestamp() { |
| return myTimestamp; |
| } |
| |
| public void lock() { |
| isLocked = true; |
| } |
| |
| @Nullable |
| public String getLabel() { |
| return accessChanges(new Producer<String>() { |
| @Override |
| public String produce() { |
| for (Change each : myChanges) { |
| if (each instanceof PutLabelChange) { |
| return ((PutLabelChange)each).getName(); |
| } |
| } |
| return null; |
| } |
| }); |
| } |
| |
| public int getLabelColor() { |
| return accessChanges(new Producer<Integer>() { |
| @Override |
| public Integer produce() { |
| for (Change each : myChanges) { |
| if (each instanceof PutSystemLabelChange) { |
| return ((PutSystemLabelChange)each).getColor(); |
| } |
| } |
| return -1; |
| } |
| }); |
| } |
| |
| public void addChange(final Change c) { |
| LocalHistoryLog.LOG.assertTrue(!isLocked, "Changset is already locked"); |
| accessChanges(new Runnable() { |
| @Override |
| public void run() { |
| myChanges.add(c); |
| } |
| }); |
| } |
| |
| public List<Change> getChanges() { |
| return accessChanges(new Producer<List<Change>>() { |
| @Override |
| public List<Change> produce() { |
| if (isLocked) return myChanges; |
| return Collections.unmodifiableList(new ArrayList<Change>(myChanges)); |
| } |
| }); |
| } |
| |
| public boolean isEmpty() { |
| return accessChanges(new Producer<Boolean>() { |
| @Override |
| public Boolean produce() { |
| return myChanges.isEmpty(); |
| } |
| }); |
| } |
| |
| public boolean affectsPath(final String paths) { |
| return accessChanges(new Producer<Boolean>() { |
| @Override |
| public Boolean produce() { |
| for (Change c : myChanges) { |
| if (c.affectsPath(paths)) return true; |
| } |
| return false; |
| } |
| }); |
| } |
| |
| public boolean isCreationalFor(final String path) { |
| return accessChanges(new Producer<Boolean>() { |
| @Override |
| public Boolean produce() { |
| for (Change c : myChanges) { |
| if (c.isCreationalFor(path)) return true; |
| } |
| return false; |
| } |
| }); |
| } |
| |
| public List<Content> getContentsToPurge() { |
| return accessChanges(new Producer<List<Content>>() { |
| @Override |
| public List<Content> produce() { |
| List<Content> result = new ArrayList<Content>(); |
| for (Change c : myChanges) { |
| result.addAll(c.getContentsToPurge()); |
| } |
| return result; |
| } |
| }); |
| } |
| |
| public boolean isContentChangeOnly() { |
| return accessChanges(new Producer<Boolean>() { |
| @Override |
| public Boolean produce() { |
| return myChanges.size() == 1 && getFirstChange() instanceof ContentChange; |
| } |
| }); |
| } |
| |
| public boolean isLabelOnly() { |
| return accessChanges(new Producer<Boolean>() { |
| @Override |
| public Boolean produce() { |
| return myChanges.size() == 1 && getFirstChange() instanceof PutLabelChange; |
| } |
| }); |
| } |
| |
| public Change getFirstChange() { |
| return accessChanges(new Producer<Change>() { |
| @Override |
| public Change produce() { |
| return myChanges.get(0); |
| } |
| }); |
| } |
| |
| public Change getLastChange() { |
| return accessChanges(new Producer<Change>() { |
| @Override |
| public Change produce() { |
| return myChanges.get(myChanges.size() - 1); |
| } |
| }); |
| } |
| |
| public List<String> getAffectedPaths() { |
| return accessChanges(new Producer<List<String>>() { |
| @Override |
| public List<String> produce() { |
| List<String> result = new SmartList<String>(); |
| for (Change each : myChanges) { |
| if (each instanceof StructuralChange) { |
| result.add(((StructuralChange)each).getPath()); |
| } |
| } |
| return result; |
| } |
| }); |
| } |
| |
| public String toString() { |
| return accessChanges(new Producer<String>() { |
| @Override |
| public String produce() { |
| return myChanges.toString(); |
| } |
| }); |
| } |
| |
| public long getId() { |
| return myId; |
| } |
| |
| @Override |
| public final boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| ChangeSet change = (ChangeSet)o; |
| |
| if (myId != change.myId) return false; |
| |
| return true; |
| } |
| |
| @Override |
| public final int hashCode() { |
| return (int)(myId ^ (myId >>> 32)); |
| } |
| |
| public void accept(ChangeVisitor v) throws ChangeVisitor.StopVisitingException { |
| if (isLocked) { |
| doAccept(v); |
| return; |
| } |
| |
| synchronized (myChanges) { |
| doAccept(v); |
| } |
| } |
| |
| private void doAccept(ChangeVisitor v) throws ChangeVisitor.StopVisitingException { |
| v.begin(this); |
| for (Change c : ContainerUtil.iterateBackward(myChanges)) { |
| c.accept(v); |
| } |
| v.end(this); |
| } |
| |
| private <T> T accessChanges(@NotNull Producer<T> func) { |
| if (isLocked) { |
| //noinspection ConstantConditions |
| return func.produce(); |
| } |
| |
| synchronized (myChanges) { |
| //noinspection ConstantConditions |
| return func.produce(); |
| } |
| } |
| |
| private void accessChanges(@NotNull final Runnable func) { |
| accessChanges(new Producer<Object>() { |
| @Override |
| public Object produce() { |
| func.run(); |
| return null; |
| } |
| }); |
| } |
| } |