001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hdfs.protocol;
019
020import java.util.Arrays;
021import java.util.Collections;
022import java.util.List;
023
024import org.apache.hadoop.fs.Path;
025import org.apache.hadoop.hdfs.DFSUtil;
026
027import com.google.common.base.Objects;
028
029/**
030 * This class represents to end users the difference between two snapshots of 
031 * the same directory, or the difference between a snapshot of the directory and
032 * its current state. Instead of capturing all the details of the diff, this
033 * class only lists where the changes happened and their types.
034 */
035public class SnapshotDiffReport {
036  private final static String LINE_SEPARATOR = System.getProperty(
037      "line.separator", "\n");
038
039  /**
040   * Types of the difference, which include CREATE, MODIFY, DELETE, and RENAME.
041   * Each type has a label for representation: +/M/-/R represent CREATE, MODIFY,
042   * DELETE, and RENAME respectively.
043   */
044  public enum DiffType {
045    CREATE("+"),     
046    MODIFY("M"),    
047    DELETE("-"), 
048    RENAME("R");
049    
050    private final String label;
051    
052    private DiffType(String label) {
053      this.label = label;
054    }
055    
056    public String getLabel() {
057      return label;
058    }
059    
060    public static DiffType getTypeFromLabel(String label) {
061      if (label.equals(CREATE.getLabel())) {
062        return CREATE;
063      } else if (label.equals(MODIFY.getLabel())) {
064        return MODIFY;
065      } else if (label.equals(DELETE.getLabel())) {
066        return DELETE;
067      } else if (label.equals(RENAME.getLabel())) {
068        return RENAME;
069      }
070      return null;
071    }
072  };
073  
074  /**
075   * Representing the full path and diff type of a file/directory where changes
076   * have happened.
077   */
078  public static class DiffReportEntry {
079    /** The type of the difference. */
080    private final DiffType type;
081    /**
082     * The relative path (related to the snapshot root) of 1) the file/directory
083     * where changes have happened, or 2) the source file/dir of a rename op.
084     */
085    private final byte[] sourcePath;
086    private final byte[] targetPath;
087
088    public DiffReportEntry(DiffType type, byte[] sourcePath) {
089      this(type, sourcePath, null);
090    }
091
092    public DiffReportEntry(DiffType type, byte[][] sourcePathComponents) {
093      this(type, sourcePathComponents, null);
094    }
095
096    public DiffReportEntry(DiffType type, byte[] sourcePath, byte[] targetPath) {
097      this.type = type;
098      this.sourcePath = sourcePath;
099      this.targetPath = targetPath;
100    }
101    
102    public DiffReportEntry(DiffType type, byte[][] sourcePathComponents,
103        byte[][] targetPathComponents) {
104      this.type = type;
105      this.sourcePath = DFSUtil.byteArray2bytes(sourcePathComponents);
106      this.targetPath = targetPathComponents == null ? null : DFSUtil
107          .byteArray2bytes(targetPathComponents);
108    }
109    
110    @Override
111    public String toString() {
112      String str = type.getLabel() + "\t" + getPathString(sourcePath);
113      if (type == DiffType.RENAME) {
114        str += " -> " + getPathString(targetPath);
115      }
116      return str;
117    }
118    
119    public DiffType getType() {
120      return type;
121    }
122
123    static String getPathString(byte[] path) {
124      String pathStr = DFSUtil.bytes2String(path);
125      if (pathStr.isEmpty()) {
126        return Path.CUR_DIR;
127      } else {
128        return Path.CUR_DIR + Path.SEPARATOR + pathStr;
129      }
130    }
131
132    public byte[] getSourcePath() {
133      return sourcePath;
134    }
135
136    public byte[] getTargetPath() {
137      return targetPath;
138    }
139
140    @Override
141    public boolean equals(Object other) {
142      if (this == other) {
143        return true;
144      } 
145      if (other != null && other instanceof DiffReportEntry) {
146        DiffReportEntry entry = (DiffReportEntry) other;
147        return type.equals(entry.getType())
148            && Arrays.equals(sourcePath, entry.getSourcePath())
149            && Arrays.equals(targetPath, entry.getTargetPath());
150      }
151      return false;
152    }
153    
154    @Override
155    public int hashCode() {
156      return Objects.hashCode(getSourcePath(), getTargetPath());
157    }
158  }
159  
160  /** snapshot root full path */
161  private final String snapshotRoot;
162
163  /** start point of the diff */
164  private final String fromSnapshot;
165  
166  /** end point of the diff */
167  private final String toSnapshot;
168  
169  /** list of diff */
170  private final List<DiffReportEntry> diffList;
171  
172  public SnapshotDiffReport(String snapshotRoot, String fromSnapshot,
173      String toSnapshot, List<DiffReportEntry> entryList) {
174    this.snapshotRoot = snapshotRoot;
175    this.fromSnapshot = fromSnapshot;
176    this.toSnapshot = toSnapshot;
177    this.diffList = entryList != null ? entryList : Collections
178        .<DiffReportEntry> emptyList();
179  }
180  
181  /** @return {@link #snapshotRoot}*/
182  public String getSnapshotRoot() {
183    return snapshotRoot;
184  }
185
186  /** @return {@link #fromSnapshot} */
187  public String getFromSnapshot() {
188    return fromSnapshot;
189  }
190
191  /** @return {@link #toSnapshot} */
192  public String getLaterSnapshotName() {
193    return toSnapshot;
194  }
195  
196  /** @return {@link #diffList} */
197  public List<DiffReportEntry> getDiffList() {
198    return diffList;
199  }
200  
201  @Override
202  public String toString() {
203    StringBuilder str = new StringBuilder();
204    String from = fromSnapshot == null || fromSnapshot.isEmpty() ? 
205        "current directory" : "snapshot " + fromSnapshot;
206    String to = toSnapshot == null || toSnapshot.isEmpty() ? "current directory"
207        : "snapshot " + toSnapshot;
208    str.append("Difference between " + from + " and " + to
209        + " under directory " + snapshotRoot + ":" + LINE_SEPARATOR);
210    for (DiffReportEntry entry : diffList) {
211      str.append(entry.toString() + LINE_SEPARATOR);
212    }
213    return str.toString();
214  }
215}