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     */
018    package org.apache.hadoop.hdfs.protocol;
019    
020    import java.util.Arrays;
021    import java.util.Collections;
022    import java.util.List;
023    
024    import org.apache.hadoop.fs.Path;
025    import org.apache.hadoop.hdfs.DFSUtil;
026    
027    import 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     */
035    public 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    }