/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.zeppelin.notebook; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; /** * Represents a folder of Notebook. ID of the folder is a normalized path of it. * 'normalized path' means the path that removed '/' from the beginning and the end of the path. * e.g. "a/b/c", but not "/a/b/c", "a/b/c/" or "/a/b/c/". * One exception can be the root folder, which is '/'. */ public class Folder { public static final String ROOT_FOLDER_ID = "/"; public static final String TRASH_FOLDER_ID = "~Trash"; public static final String TRASH_FOLDER_CONFLICT_INFIX = " "; private String id; private Folder parent; private Map<String, Folder> children = new LinkedHashMap<>(); // key: noteId private final Map<String, Note> notes = new LinkedHashMap<>(); private List<FolderListener> listeners = new LinkedList<>(); private static final Logger logger = LoggerFactory.getLogger(Folder.class); public Folder(String id) { this.id = id; } public String getId() { return id; } public String getName() { if (isRoot()) return ROOT_FOLDER_ID; String path = getId(); int lastSlashIndex = path.lastIndexOf("/"); if (lastSlashIndex < 0) { // This folder is under the root return path; } return path.substring(lastSlashIndex + 1); } public String getParentFolderId() { if (isRoot()) return ROOT_FOLDER_ID; int lastSlashIndex = getId().lastIndexOf("/"); // The root folder if (lastSlashIndex < 0) { return Folder.ROOT_FOLDER_ID; } return getId().substring(0, lastSlashIndex); } public static String normalizeFolderId(String id) { id = id.trim(); id = id.replace("\\", "/"); while (id.contains("///")) { id = id.replaceAll("///", "/"); } id = id.replaceAll("//", "/"); if (id.equals(ROOT_FOLDER_ID)) { return ROOT_FOLDER_ID; } if (id.charAt(0) == '/') { id = id.substring(1); } if (id.charAt(id.length() - 1) == '/') { id = id.substring(0, id.length() - 1); } return id; } /** * Rename this folder as well as the notes and the children belong to it * * @param newId */ public void rename(String newId) { if (isRoot()) // root folder cannot be renamed return; String oldId = getId(); id = normalizeFolderId(newId); logger.info("Rename {} to {}", oldId, getId()); synchronized (notes) { for (Note note : notes.values()) { String noteName = note.getNameWithoutPath(); String newNotePath; if (newId.equals(ROOT_FOLDER_ID)) { newNotePath = noteName; } else { newNotePath = newId + "/" + noteName; } note.setName(newNotePath); } } for (Folder child : children.values()) { child.rename(getId() + "/" + child.getName()); } notifyRenamed(oldId); } /** * Merge folder's notes and child folders * * @param folder */ public void merge(Folder folder) { logger.info("Merge {} into {}", folder.getId(), getId()); addNotes(folder.getNotes()); } public void addFolderListener(FolderListener listener) { listeners.add(listener); } public void notifyRenamed(String oldFolderId) { for (FolderListener listener : listeners) { listener.onFolderRenamed(this, oldFolderId); } } public Folder getParent() { return parent; } public Map<String, Folder> getChildren() { return children; } public void setParent(Folder parent) { logger.info("Set parent of {} to {}", getId(), parent.getId()); this.parent = parent; } public void addChild(Folder child) { if (child == this) // prevent the root folder from setting itself as child return; children.put(child.getId(), child); } public void removeChild(String folderId) { logger.info("Remove child {} from {}", folderId, getId()); children.remove(folderId); } public void addNote(Note note) { logger.info("Add note {} to folder {}", note.getId(), getId()); synchronized (notes) { notes.put(note.getId(), note); } } public void addNotes(List<Note> newNotes) { synchronized (notes) { for (Note note : newNotes) { notes.put(note.getId(), note); } } } public void removeNote(Note note) { logger.info("Remove note {} from folder {}", note.getId(), getId()); synchronized (notes) { notes.remove(note.getId()); } } public List<Note> getNotes() { return new LinkedList<>(notes.values()); } public List<Note> getNotesRecursively() { List<Note> notes = getNotes(); for (Folder child : children.values()) { notes.addAll(child.getNotesRecursively()); } return notes; } public int countNotes() { return notes.size(); } public boolean hasChild() { return children.size() > 0; } boolean isRoot() { return getId().equals(ROOT_FOLDER_ID); } public boolean isTrash() { if (isRoot()) return false; return getId().split("/")[0].equals(TRASH_FOLDER_ID); } }