com.newatlanta.appengine.nio.file.GaePath.java Source code

Java tutorial

Introduction

Here is the source code for com.newatlanta.appengine.nio.file.GaePath.java

Source

/*
 * Copyright 2009 New Atlanta Communications, LLC
 *
 * 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.newatlanta.appengine.nio.file;

import static com.newatlanta.appengine.nio.file.attribute.GaeFileAttributes.BASIC_VIEW;
import static com.newatlanta.appengine.nio.file.attribute.GaeFileAttributes.GAE_VIEW;
import static com.newatlanta.repackaged.java.nio.file.StandardCopyOption.ATOMIC_MOVE;
import static com.newatlanta.repackaged.java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
import static com.newatlanta.repackaged.java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static com.newatlanta.repackaged.java.nio.file.StandardOpenOption.APPEND;
import static com.newatlanta.repackaged.java.nio.file.StandardOpenOption.CREATE;
import static com.newatlanta.repackaged.java.nio.file.StandardOpenOption.CREATE_NEW;
import static com.newatlanta.repackaged.java.nio.file.StandardOpenOption.DELETE_ON_CLOSE;
import static com.newatlanta.repackaged.java.nio.file.StandardOpenOption.DSYNC;
import static com.newatlanta.repackaged.java.nio.file.StandardOpenOption.READ;
import static com.newatlanta.repackaged.java.nio.file.StandardOpenOption.SPARSE;
import static com.newatlanta.repackaged.java.nio.file.StandardOpenOption.SYNC;
import static com.newatlanta.repackaged.java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static com.newatlanta.repackaged.java.nio.file.StandardOpenOption.WRITE;

import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.locks.Lock;

import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.Selectors;

import com.newatlanta.appengine.locks.ExclusiveLock;
import com.newatlanta.appengine.nio.channels.GaeFileChannel;
import com.newatlanta.appengine.nio.file.attribute.GaeFileAttributeView;
import com.newatlanta.appengine.nio.file.attribute.GaeFileAttributes;
import com.newatlanta.appengine.vfs.provider.GaeFileObject;
import com.newatlanta.appengine.vfs.provider.GaeVFS;
import com.newatlanta.repackaged.java.nio.channels.FileChannel;
import com.newatlanta.repackaged.java.nio.file.AccessDeniedException;
import com.newatlanta.repackaged.java.nio.file.AccessMode;
import com.newatlanta.repackaged.java.nio.file.AtomicMoveNotSupportedException;
import com.newatlanta.repackaged.java.nio.file.CopyOption;
import com.newatlanta.repackaged.java.nio.file.DirectoryNotEmptyException;
import com.newatlanta.repackaged.java.nio.file.DirectoryStream;
import com.newatlanta.repackaged.java.nio.file.FileAlreadyExistsException;
import com.newatlanta.repackaged.java.nio.file.FileStore;
import com.newatlanta.repackaged.java.nio.file.FileSystem;
import com.newatlanta.repackaged.java.nio.file.InvalidPathException;
import com.newatlanta.repackaged.java.nio.file.LinkOption;
import com.newatlanta.repackaged.java.nio.file.NoSuchFileException;
import com.newatlanta.repackaged.java.nio.file.NotDirectoryException;
import com.newatlanta.repackaged.java.nio.file.OpenOption;
import com.newatlanta.repackaged.java.nio.file.Path;
import com.newatlanta.repackaged.java.nio.file.ProviderMismatchException;
import com.newatlanta.repackaged.java.nio.file.WatchKey;
import com.newatlanta.repackaged.java.nio.file.WatchService;
import com.newatlanta.repackaged.java.nio.file.DirectoryStream.Filter;
import com.newatlanta.repackaged.java.nio.file.WatchEvent.Kind;
import com.newatlanta.repackaged.java.nio.file.WatchEvent.Modifier;
import com.newatlanta.repackaged.java.nio.file.attribute.BasicFileAttributeView;
import com.newatlanta.repackaged.java.nio.file.attribute.FileAttribute;
import com.newatlanta.repackaged.java.nio.file.attribute.FileAttributeView;

/**
 * Implements {@linkplain com.newatlanta.repackaged.java.nio.file.Path} for GaeVFS.
 * 
 * @author <a href="mailto:vbonfanti@gmail.com">Vince Bonfanti</a>
 */
public class GaePath extends Path {

    private FileSystem fileSystem;
    private FileObject fileObject;
    private String path;
    private Lock lock; // access via getLock()

    public GaePath(FileSystem fileSystem, String path) {
        this.fileSystem = fileSystem;
        this.path = path;
        try {
            fileObject = GaeVFS.resolveFile(path);
        } catch (FileSystemException e) {
            throw new InvalidPathException(path, e.toString());
        }
    }

    GaePath(FileSystem fileSystem, FileObject fileObject) {
        this.fileSystem = fileSystem;
        this.fileObject = fileObject;
        path = fileObject.getName().getPath();
    }

    private synchronized Lock getLock() {
        if (lock == null) {
            lock = new ExclusiveLock(fileObject.getName().getPath() + ".GaePath.lock");
        }
        return lock;
    }

    @Override
    public void checkAccess(AccessMode... modes) throws IOException {
        if (!fileObject.exists()) {
            throw new NoSuchFileException(toString());
        }
        for (AccessMode mode : modes) {
            if (((mode == AccessMode.READ) && !fileObject.isReadable())
                    || ((mode == AccessMode.WRITE) && !fileObject.isWriteable()) || (mode == AccessMode.EXECUTE)) {
                throw new AccessDeniedException(toString(), null, mode.toString());
            }
        }
    }

    @Override
    public boolean exists() {
        try {
            return fileObject.exists();
        } catch (IOException e) {
            return false;
        }
    }

    @Override
    public boolean notExists() {
        try {
            return !fileObject.exists();
        } catch (IOException e) {
            return false; // unknown
        }
    }

    @Override
    public int compareTo(Path other) {
        // throws NullPointerException and ClassCastException per Comparable
        return path.compareTo(((GaePath) other).path);
    }

    @Override
    public boolean equals(Object other) {
        if ((other == null) || !(other instanceof GaePath)) {
            return false;
        }
        return path.equals(((GaePath) other).path);
    }

    @Override
    public int hashCode() {
        return path.hashCode();
    }

    @Override
    public boolean isSameFile(Path other) throws IOException {
        if ((other == null) || !(other instanceof GaePath)) {
            return false;
        }
        if ((this == other) || this.equals(other)) {
            return true;
        }
        return fileObject.getName().getPath().equals(((GaePath) other).fileObject.getName().getPath());
    }

    @Override
    public Path createDirectory(FileAttribute<?>... attrs) throws IOException {
        if (attrs.length > 0) {
            throw new UnsupportedOperationException(attrs[0].name());
        }
        GaePath parent = getParent();
        if (parent != null) {
            parent.getLock().lock(); // prevent delete or rename of parent
            try {
                parent.checkAccess(AccessMode.WRITE);
                return createDir(attrs);
            } finally {
                parent.lock.unlock();
            }
        } else {
            // root directory should always exist
            return createDir(attrs);
        }
    }

    private Path createDir(FileAttribute<?>... attrs) throws IOException {
        if (notExists()) {
            fileObject.createFolder();
            return this;
        } else {
            throw new FileAlreadyExistsException(toString(), null, null);
        }
    }

    @Override
    public Path createFile(FileAttribute<?>... attrs) throws IOException {
        for (FileAttribute<?> attr : attrs) {
            if (attr.name().equals(GaeFileAttributes.BLOCK_SIZE)) {
                GaeVFS.setBlockSize(fileObject, (Integer) attr.value());
            } else {
                throw new UnsupportedOperationException(attr.name());
            }
        }
        GaePath parent = getParent();
        parent.getLock().lock(); // prevent delete or rename of parent
        try {
            parent.checkAccess(AccessMode.WRITE);
            if (notExists()) {
                fileObject.createFile();
                return this;
            } else {
                throw new FileAlreadyExistsException(toString(), null, null);
            }
        } finally {
            parent.lock.unlock();
        }
    }

    @Override
    public Path createLink(Path existing) throws IOException {
        throw new UnsupportedOperationException();
    }

    @Override
    public Path createSymbolicLink(Path target, FileAttribute<?>... attrs) throws IOException {
        throw new UnsupportedOperationException();
    }

    @Override
    public void delete() throws IOException {
        checkAccess(AccessMode.WRITE);
        if (fileObject.getType().hasChildren()) { // directory
            getLock().lock(); // prevent rename or create children
            try {
                if (fileObject.getChildren().length > 0) { // not empty
                    throw new DirectoryNotEmptyException(toString());
                }
                fileObject.delete();
            } finally {
                lock.unlock();
            }
        } else { // file
            fileObject.close();
            fileObject.delete();
        }
    }

    @Override
    public void deleteIfExists() throws IOException {
        if (exists()) {
            delete();
        }
    }

    @Override
    public FileStore getFileStore() throws IOException {
        return GaeFileStore.getInstance();
    }

    @Override
    public FileSystem getFileSystem() {
        return fileSystem;
    }

    @Override
    public Path getName() {
        // TODO: THIS IS NOT RIGHT--THE ABSOLUTE PATH IS INCORRECT
        return new GaePath(fileSystem, fileObject.getName().getBaseName());
    }

    private static final String PATH_DELIMS = "/\\"; // include Windows for development

    @Override
    public Path getName(int index) {
        StringTokenizer st = new StringTokenizer(path, PATH_DELIMS);
        int numEntries = st.countTokens();
        if ((index < 0) || (index >= numEntries)) {
            throw new IllegalArgumentException();
        }
        for (int i = 0; i < numEntries; i++) {
            st.nextToken();
        }
        return new GaePath(fileSystem, st.nextToken());
    }

    @Override
    public int getNameCount() {
        return new StringTokenizer(path, PATH_DELIMS).countTokens();
    }

    @Override
    public Iterator<Path> iterator() {
        StringTokenizer st = new StringTokenizer(path, PATH_DELIMS);
        List<Path> entryList = new ArrayList<Path>();
        while (st.hasMoreTokens()) {
            entryList.add(new GaePath(fileSystem, st.nextToken()));
        }
        return entryList.iterator();
    }

    @Override
    public Path subpath(int beginIndex, int endIndex) {
        StringTokenizer st = new StringTokenizer(path, PATH_DELIMS, true);
        int numEntries = st.countTokens();
        if ((beginIndex < 0) || (beginIndex >= numEntries) || (endIndex <= beginIndex) || (endIndex > numEntries)) {
            throw new IllegalArgumentException();
        }
        StringBuffer sb = new StringBuffer();
        for (int i = beginIndex; i < endIndex; i++) {
            sb.append(st.nextToken());
        }
        return new GaePath(fileSystem, sb.toString());
    }

    @Override
    public GaePath getParent() {
        try {
            FileObject parentObject = fileObject.getParent();
            if (parentObject == null) {
                return null;
            }
            return new GaePath(fileSystem, parentObject);
        } catch (FileSystemException e) {
            throw new InvalidPathException(fileObject.getName().getParent().getPath(), e.toString());
        }
    }

    @Override
    public Path getRoot() {
        return new GaePath(fileSystem, fileObject.getName().getRootURI());
    }

    @Override
    public boolean isAbsolute() {
        return startsWith(getRoot());
    }

    @Override
    public boolean isHidden() throws IOException {
        return false; // hidden files not supported
    }

    @Override
    public Path copyTo(Path target, CopyOption... options) throws IOException {
        if (!(target instanceof GaePath)) {
            throw new ProviderMismatchException();
        }
        checkAccess(AccessMode.READ);
        Set<CopyOption> optionSet = checkCopyOptions(options);
        if (isSameFile(target, optionSet.contains(REPLACE_EXISTING))) {
            return target;
        }
        if (fileObject.getType().hasChildren()) {
            target.deleteIfExists(); // fails for non-empty directory
            target.createDirectory();
        } else {
            ((GaePath) target).fileObject.copyFrom(fileObject, Selectors.SELECT_SELF);
            if (optionSet.contains(COPY_ATTRIBUTES)) {
                ((GaePath) target).fileObject.getContent()
                        .setLastModifiedTime(fileObject.getContent().getLastModifiedTime());
            }
        }
        return target;
    }

    @Override
    public Path moveTo(Path target, CopyOption... options) throws IOException {
        if (!(target instanceof GaePath)) {
            throw new ProviderMismatchException();
        }
        checkAccess(AccessMode.READ, AccessMode.WRITE);
        Set<CopyOption> optionSet = checkCopyOptions(options);
        if (isSameFile(target, optionSet.contains(REPLACE_EXISTING))) {
            return target;
        }
        if (fileObject.getType().hasChildren()) {
            if (fileObject.getChildren().length > 0) {
                // moving a non-empty directory requires moving the children,
                // so throw an exception per the javadocs
                throw new DirectoryNotEmptyException(path);
            }
            getLock().lock(); // prevent creation of children while moving
            try {
                fileObject.moveTo(((GaePath) target).fileObject);
            } finally {
                lock.unlock();
            }
        } else {
            fileObject.moveTo(((GaePath) target).fileObject);
        }
        return target;
    }

    private Set<CopyOption> checkCopyOptions(CopyOption... options) throws IOException {
        Set<CopyOption> optionSet = new HashSet<CopyOption>();
        for (CopyOption option : options) {
            if (option == REPLACE_EXISTING) {
                optionSet.add(option);
            } else if (option == COPY_ATTRIBUTES) {
                optionSet.add(option);
            } else if (option == ATOMIC_MOVE) {
                // paths are stored in entity keys and GAE does not allow keys to
                // be modified after the entity is created; therefore, GaeVFS does
                // not support rename, but must always do copy-then-delete
                throw new AtomicMoveNotSupportedException(path, null, null);
            } else {
                throw new UnsupportedOperationException(option.toString());
            }
        }
        return optionSet;
    }

    private boolean isSameFile(Path target, boolean replaceExisting) throws IOException {
        if (target.exists()) {
            if (target.isSameFile(this)) {
                return true;
            }
            if (!replaceExisting) {
                throw new FileAlreadyExistsException(path, target.toString(), null);
            }
            target.checkAccess(AccessMode.WRITE);
        }
        return false;
    }

    @Override
    public DirectoryStream<Path> newDirectoryStream() throws IOException {
        checkAccess(AccessMode.READ);
        if (!fileObject.getType().hasChildren()) {
            throw new NotDirectoryException(toString());
        }
        return new GaeDirectoryStream(fileSystem, fileObject.getChildren());
    }

    @Override
    public DirectoryStream<Path> newDirectoryStream(String glob) throws IOException {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public DirectoryStream<Path> newDirectoryStream(Filter<? super Path> filter) throws IOException {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public FileChannel newByteChannel(Set<? extends OpenOption> options, FileAttribute<?>... attrs)
            throws IOException {
        if (!(fileObject instanceof GaeFileObject)) {
            throw new ProviderMismatchException();
        }
        checkByteChannelOpenOptions(options);
        if (options.contains(CREATE_NEW)) {
            createFile(attrs); // throws FileAlreadyExistsException
        } else if (options.contains(CREATE) && notExists()) {
            try {
                createFile(attrs);
            } catch (FileAlreadyExistsException ignore) {
            }
        }
        if (options.contains(WRITE)) {
            checkAccess(AccessMode.READ, AccessMode.WRITE);
        } else {
            checkAccess(AccessMode.READ);
        }
        return new GaeFileChannel((GaeFileObject) fileObject, options);
    }

    @SuppressWarnings("unchecked")
    private static void checkByteChannelOpenOptions(Set<? extends OpenOption> options) {
        if (options.contains(SYNC)) {
            throw new UnsupportedOperationException(SYNC.name());
        }
        if (options.contains(DSYNC)) {
            throw new UnsupportedOperationException(DSYNC.name());
        }
        if (options.contains(DELETE_ON_CLOSE)) {
            throw new UnsupportedOperationException(DELETE_ON_CLOSE.name());
        }
        if (options.contains(APPEND)) {
            if (options.contains(READ)) {
                throw new IllegalArgumentException(
                        "Cannot specify both " + APPEND.name() + " and " + READ.name() + " options.");
            }
            if (options.contains(TRUNCATE_EXISTING)) {
                throw new IllegalArgumentException(
                        "Cannot specify both " + APPEND.name() + " and " + TRUNCATE_EXISTING.name() + " options.");
            }
            ((Set<OpenOption>) options).add(WRITE); // APPEND implies WRITE
        }
        if (!options.contains(WRITE)) { // some options ignored if not writing
            options.remove(TRUNCATE_EXISTING);
            options.remove(CREATE);
            options.remove(CREATE_NEW);
            options.remove(SPARSE); // ignored if not creating new file
        }
        if (options.contains(SPARSE)) {
            throw new UnsupportedOperationException(SPARSE.name());
        }
    }

    @Override
    public FileChannel newByteChannel(OpenOption... options) throws IOException {
        return newByteChannel(getOpenOptionSet(options), new FileAttribute[0]);
    }

    private static Set<OpenOption> getOpenOptionSet(OpenOption... options) {
        Set<OpenOption> optionSet = new HashSet<OpenOption>(options.length);
        Collections.addAll(optionSet, options);
        return optionSet;
    }

    public InputStream newInputStream(OpenOption... options) throws IOException {
        for (OpenOption option : options) {
            if (option != READ) {
                throw new UnsupportedOperationException(option.toString());
            }
        }
        checkAccess(AccessMode.READ);
        return fileObject.getContent().getInputStream();
    }

    @Override
    public OutputStream newOutputStream(OpenOption... options) throws IOException {
        Set<OpenOption> optionSet = checkOutputStreamOpenOptions(options);
        if (optionSet.contains(CREATE_NEW)) {
            createFile(); // throws FileAlreadyExistsException
        } else if (optionSet.contains(CREATE) && notExists()) {
            try {
                createFile();
            } catch (FileAlreadyExistsException ignore) {
            }
        }
        checkAccess(AccessMode.WRITE);
        return fileObject.getContent().getOutputStream(optionSet.contains(APPEND));
    }

    private static Set<OpenOption> checkOutputStreamOpenOptions(OpenOption... options) {
        Set<OpenOption> optionSet = getOpenOptionSet(options);
        if (optionSet.contains(READ)) {
            throw new IllegalArgumentException(READ.name());
        }
        if (optionSet.contains(SYNC)) {
            throw new UnsupportedOperationException(SYNC.name());
        }
        if (optionSet.contains(DSYNC)) {
            throw new UnsupportedOperationException(DSYNC.name());
        }
        if (optionSet.contains(DELETE_ON_CLOSE)) {
            throw new UnsupportedOperationException(DELETE_ON_CLOSE.name());
        }
        if (optionSet.contains(SPARSE)) {
            throw new UnsupportedOperationException(SPARSE.name());
        }
        if (optionSet.contains(TRUNCATE_EXISTING)) {
            throw new UnsupportedOperationException(TRUNCATE_EXISTING.name());
        }
        return optionSet;
    }

    @Override
    public Path normalize() {
        // FileObject paths are normalized upon creation
        return new GaePath(fileSystem, fileObject.getName().getPath());
    }

    @Override
    public Path readSymbolicLink() throws IOException {
        throw new UnsupportedOperationException();
    }

    @Override
    public WatchKey register(WatchService watcher, Kind<?>[] events, Modifier... modifiers) throws IOException {
        throw new UnsupportedOperationException();
    }

    @Override
    public WatchKey register(WatchService watcher, Kind<?>... events) throws IOException {
        throw new UnsupportedOperationException();
    }

    @Override
    public Path resolve(Path other) {
        if (other == null) {
            return this;
        }
        if (!(other instanceof GaePath)) {
            throw new ProviderMismatchException();
        }
        if (other.isAbsolute()) {
            return other;
        }
        return resolve(((GaePath) other).path);
    }

    @Override
    public Path resolve(String other) {
        try {
            return new GaePath(fileSystem, fileObject.resolveFile(other));
        } catch (FileSystemException e) {
            throw new InvalidPathException(other, e.toString());
        }
    }

    @Override
    public Path relativize(Path other) {
        if (!(other instanceof GaePath)) {
            throw new ProviderMismatchException();
        }
        if (this.equals(other)) {
            return null;
        }
        try {
            return new GaePath(fileSystem,
                    fileObject.getName().getRelativeName(((GaePath) other).fileObject.getName()));
        } catch (FileSystemException e) {
            throw new InvalidPathException(other.toString(), e.toString());
        }
    }

    @Override
    public boolean startsWith(Path other) {
        if (!(other instanceof GaePath)) {
            throw new ProviderMismatchException();
        }
        return path.startsWith(((GaePath) other).path);
    }

    @Override
    public boolean endsWith(Path other) {
        if (!(other instanceof GaePath)) {
            throw new ProviderMismatchException();
        }
        return path.endsWith(((GaePath) other).path);
    }

    @Override
    public Path toAbsolutePath() {
        return new GaePath(fileSystem, fileObject.getName().getURI());
    }

    @Override
    public Path toRealPath(boolean resolveLinks) throws IOException {
        return toAbsolutePath();
    }

    @Override
    public String toString() {
        return path;
    }

    @Override
    public URI toUri() {
        try {
            return new URI(fileObject.getName().getURI());
        } catch (Exception e) {
            throw new IOError(e);
        }
    }

    @SuppressWarnings("unchecked")
    public <V extends FileAttributeView> V getFileAttributeView(Class<V> type, LinkOption... options) {
        if (type == BasicFileAttributeView.class) {
            return (V) new GaeFileAttributeView(BASIC_VIEW, fileObject);
        } else if (type == GaeFileAttributeView.class) {
            return (V) new GaeFileAttributeView(GAE_VIEW, fileObject);
        }
        return null;
    }

    private GaeFileAttributeView getGaeFileAttributeView(String viewName) {
        if (BASIC_VIEW.equals(viewName) || GAE_VIEW.equals(viewName)) {
            return new GaeFileAttributeView(viewName, fileObject);
        }
        return null;
    }

    public Object getAttribute(String attribute, LinkOption... options) throws IOException {
        AttributeName attr = new AttributeName(attribute);
        GaeFileAttributeView attrView = getGaeFileAttributeView(attr.viewName);
        if (attrView == null) {
            return null;
        }
        return attrView.readAttributes().getAttribute(attr.viewName, attr.attrName);
    }

    public Map<String, ?> readAttributes(String attributes, LinkOption... options) throws IOException {
        AttributeName attr = new AttributeName(attributes);
        GaeFileAttributeView gaeAttrView = getGaeFileAttributeView(attr.viewName);
        if (gaeAttrView == null) {
            return new HashMap<String, Object>();
        }
        if ("*".equals(attr.attrName)) {
            return gaeAttrView.readAttributes().getSupportedAttributes(attr.viewName);
        } else {
            Map<String, Object> attrMap = new HashMap<String, Object>();
            GaeFileAttributes gaeAttrs = gaeAttrView.readAttributes();
            StringTokenizer st = new StringTokenizer(attr.attrName, ",");
            while (st.hasMoreTokens()) {
                String attrName = st.nextToken();
                Object attrValue = gaeAttrs.getAttribute(attr.viewName, attrName);
                if (attrValue != null) {
                    attrMap.put(attrName, attrValue);
                }
            }
            return attrMap;
        }
    }

    public void setAttribute(String attribute, Object value, LinkOption... options) throws IOException {
        AttributeName attr = new AttributeName(attribute);
        GaeFileAttributeView attrView = getGaeFileAttributeView(attr.viewName);
        if (attrView != null) {
            attrView.readAttributes().setAttribute(attr.viewName, attr.attrName, value);
        }
    }

    private class AttributeName {

        private String viewName;
        private String attrName;

        private AttributeName(String attribute) {
            int colonPos = attribute.indexOf(':');
            if (colonPos == -1) {
                viewName = BASIC_VIEW;
                attrName = attribute;
            } else {
                viewName = attribute.substring(0, colonPos++);
                attrName = attribute.substring(colonPos);
            }
        }
    }
}