Merge "BUG-1790: detect existing unmodifiableMap encapsulation"
authorTony Tkacik <ttkacik@cisco.com>
Wed, 10 Sep 2014 17:40:08 +0000 (17:40 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Wed, 10 Sep 2014 17:40:08 +0000 (17:40 +0000)
24 files changed:
common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/AsyncNotifyingListenableFutureTask.java
common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/CountingRejectedExecutionHandler.java
common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/DeadlockDetectingListeningExecutorService.java
common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/SettableBoolean.java [new file with mode: 0644]
common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/SettableBooleanThreadLocal.java [new file with mode: 0644]
common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/TrackingLinkedBlockingQueue.java
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/AbstractJSONCodec.java [new file with mode: 0644]
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONCodec.java [new file with mode: 0644]
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONCodecFactory.java [moved from yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/CodecFactory.java with 55% similarity]
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONLeafrefCodec.java [new file with mode: 0644]
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONNormalizedNodeStreamWriter.java
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterContext.java [new file with mode: 0644]
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterInvisibleContext.java [new file with mode: 0644]
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterListContext.java [new file with mode: 0644]
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterNamedObjectContext.java [new file with mode: 0644]
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterObjectContext.java [new file with mode: 0644]
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterQNameContext.java [new file with mode: 0644]
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterRootContext.java [new file with mode: 0644]
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterURIContext.java [new file with mode: 0644]
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStringIdentityrefCodec.java
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStringInstanceIdentifierCodec.java
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JsonParserStream.java
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/QuotedJSONCodec.java [new file with mode: 0644]
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/UnquotedJSONCodec.java [new file with mode: 0644]

index 69c94f32a35cfc1e7b1ec9bf71e6287bd88b7acc..2ba16931f18a8f687836415c8da213e2a6cc3bab 100644 (file)
@@ -8,18 +8,21 @@
 
 package org.opendaylight.yangtools.util.concurrent;
 
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.ExecutionList;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListenableFutureTask;
+
 import java.util.concurrent.Callable;
 import java.util.concurrent.Executor;
 import java.util.concurrent.FutureTask;
 
+import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.util.concurrent.ExecutionList;
-import com.google.common.util.concurrent.ListenableFuture;
-
 /**
  * A {@link FutureTask} that also implements the {@link ListenableFuture} interface similar to
  * guava's {@link ListenableFutureTask}. This class differs from ListenableFutureTask in that it
@@ -35,38 +38,91 @@ import com.google.common.util.concurrent.ListenableFuture;
  * listener Runnable would execute in the thread that completed this task, the listener
  * is executed on Executor specified on construction.
  *
+ * Also note that the use of this task may attach some (small) amount of state to the threads
+ * interacting with it. That state will not be detached automatically, but you can use
+ *  {@link #cleanStateForCurrentThread()} to clean it up.
+ *
  * @author Thomas Pantelis
+ * @author Robert Varga
  *
  * @param <V> the Future result value type
  */
 public class AsyncNotifyingListenableFutureTask<V> extends FutureTask<V> implements ListenableFuture<V> {
+    private static final class DelegatingAsyncNotifyingListenableFutureTask<V> extends AsyncNotifyingListenableFutureTask<V> {
+        /**
+         * The executor used to run listener callbacks.
+         */
+        private final Executor listenerExecutor;
+
+        private DelegatingAsyncNotifyingListenableFutureTask(final Callable<V> callable, @Nullable final Executor listenerExecutor) {
+            super(callable);
+            this.listenerExecutor = Preconditions.checkNotNull(listenerExecutor);
+        }
+
+        private DelegatingAsyncNotifyingListenableFutureTask(final Runnable runnable, @Nullable final V result,
+                @Nullable final Executor listenerExecutor) {
+            super(runnable, result);
+            this.listenerExecutor = Preconditions.checkNotNull(listenerExecutor);
+        }
+
+        @Override
+        public void addListener(final Runnable listener, final Executor executor) {
+            // Wrap the listener Runnable in a DelegatingRunnable. If the specified executor is one that
+            // runs tasks in the same thread as the caller submitting the task
+            // (e.g. {@link com.google.common.util.concurrent.MoreExecutors#sameThreadExecutor}) and the
+            // listener is executed from the #done method, then the DelegatingRunnable will detect this
+            // via the ThreadLocal and submit the listener Runnable to the listenerExecutor.
+            //
+            // On the other hand, if this task is already complete, the call to ExecutionList#add in
+            // superclass will execute the listener Runnable immediately and, since the ThreadLocal won't be set,
+            // the DelegatingRunnable will run the listener Runnable inline.
+            super.addListener(new DelegatingRunnable(listener, listenerExecutor), executor);
+        }
+    }
 
-    private static final Logger LOG = LoggerFactory.getLogger( AsyncNotifyingListenableFutureTask.class );
+    private static final class DelegatingRunnable implements Runnable {
+        private final Runnable delegate;
+        private final Executor executor;
+
+        DelegatingRunnable(final Runnable delegate, final Executor executor) {
+            this.delegate = Preconditions.checkNotNull(delegate);
+            this.executor = Preconditions.checkNotNull(executor);
+        }
+
+        @Override
+        public void run() {
+            if (ON_TASK_COMPLETION_THREAD_TL.get().isSet()) {
+                // We're running on the task completion thread so off-load to the executor.
+                LOG.trace("Submitting ListenenableFuture Runnable from thread {} to executor {}",
+                        Thread.currentThread().getName(), executor);
+                executor.execute(delegate);
+            } else {
+                // We're not running on the task completion thread so run the delegate inline.
+                LOG.trace("Executing ListenenableFuture Runnable on this thread: {}",
+                        Thread.currentThread().getName());
+                delegate.run();
+            }
+        }
+    }
+
+    private static final Logger LOG = LoggerFactory.getLogger(AsyncNotifyingListenableFutureTask.class);
 
     /**
      * ThreadLocal used to detect if the task completion thread is running the listeners.
      */
-    private static final ThreadLocal<Boolean> ON_TASK_COMPLETION_THREAD_TL = new ThreadLocal<>();
+    private static final SettableBooleanThreadLocal ON_TASK_COMPLETION_THREAD_TL = new SettableBooleanThreadLocal();
 
     /**
      *  The execution list to hold our listeners.
      */
     private final ExecutionList executionList = new ExecutionList();
 
-    /**
-     * The executor used to run listener callbacks.
-     */
-    private final Executor listenerExecutor;
-
-    private AsyncNotifyingListenableFutureTask( Callable<V> callable, @Nullable Executor listenerExecutor ) {
-        super( callable );
-        this.listenerExecutor = listenerExecutor;
+    private AsyncNotifyingListenableFutureTask(final Callable<V> callable) {
+        super(callable);
     }
 
-    private AsyncNotifyingListenableFutureTask( Runnable runnable, @Nullable V result,
-            @Nullable Executor listenerExecutor ) {
-        super( runnable, result );
-        this.listenerExecutor = listenerExecutor;
+    private AsyncNotifyingListenableFutureTask(final Runnable runnable, @Nullable final V result) {
+        super(runnable, result);
     }
 
     /**
@@ -77,9 +133,13 @@ public class AsyncNotifyingListenableFutureTask<V> extends FutureTask<V> impleme
      * @param listenerExecutor the executor used to run listener callbacks asynchronously.
      *                         If null, no executor is used.
      */
-    public static <V> AsyncNotifyingListenableFutureTask<V> create( Callable<V> callable,
-            @Nullable Executor listenerExecutor ) {
-      return new AsyncNotifyingListenableFutureTask<V>( callable, listenerExecutor );
+    public static <V> AsyncNotifyingListenableFutureTask<V> create(final Callable<V> callable,
+            @Nullable final Executor listenerExecutor) {
+        if (listenerExecutor != null) {
+            return new DelegatingAsyncNotifyingListenableFutureTask<V>(callable, listenerExecutor);
+        } else {
+            return new AsyncNotifyingListenableFutureTask<V>(callable);
+        }
     }
 
     /**
@@ -92,25 +152,26 @@ public class AsyncNotifyingListenableFutureTask<V> extends FutureTask<V> impleme
      * @param listenerExecutor the executor used to run listener callbacks asynchronously.
      *                         If null, no executor is used.
      */
-    public static <V> AsyncNotifyingListenableFutureTask<V> create( Runnable runnable, @Nullable V result,
-            @Nullable Executor listenerExecutor ) {
-      return new AsyncNotifyingListenableFutureTask<V>( runnable, result, listenerExecutor );
+    public static <V> AsyncNotifyingListenableFutureTask<V> create(final Runnable runnable, @Nullable final V result,
+            @Nullable final Executor listenerExecutor) {
+        if (listenerExecutor != null) {
+            return new DelegatingAsyncNotifyingListenableFutureTask<V>(runnable, result, listenerExecutor);
+        } else {
+            return new AsyncNotifyingListenableFutureTask<V>(runnable, result);
+        }
     }
 
     @Override
-    public void addListener( Runnable listener, Executor executor ) {
-        // If a listenerExecutor was specified on construction, wrap the listener Runnable in a
-        // DelegatingRunnable. If the specified executor is one that runs tasks in the same thread
-        // as the caller submitting the task (eg MoreExecutors#sameThreadExecutor) and the
-        // listener is executed from the #done method, then the DelegatingRunnable will detect this
-        // via the ThreadLocal and submit the listener Runnable to the listenerExecutor.
-        //
-        // On the other hand, if this task is already complete, the call to ExecutionList#add below
-        // will execute the listener Runnable immediately and, since the ThreadLocal won't be set,
-        // the DelegatingRunnable will run the listener Runnable inline.
-
-        executionList.add( listenerExecutor == null ? listener :
-            new DelegatingRunnable( listener, listenerExecutor ), executor );
+    public void addListener(@Nonnull final Runnable listener, final Executor executor) {
+        executionList.add(listener, executor);
+    }
+
+    /**
+     * Remove the state which may have attached to the calling thread. If no state
+     * was attached this method does nothing.
+     */
+    public static void cleanStateForCurrentThread() {
+        ON_TASK_COMPLETION_THREAD_TL.remove();
     }
 
     /**
@@ -118,37 +179,13 @@ public class AsyncNotifyingListenableFutureTask<V> extends FutureTask<V> impleme
      */
     @Override
     protected void done() {
-        ON_TASK_COMPLETION_THREAD_TL.set( Boolean.TRUE );
+        final SettableBoolean b = ON_TASK_COMPLETION_THREAD_TL.get();
+        b.set();
+
         try {
             executionList.execute();
         } finally {
-            ON_TASK_COMPLETION_THREAD_TL.remove();
-        }
-    }
-
-    private static class DelegatingRunnable implements Runnable {
-
-        private final Runnable delegate;
-        private final Executor executor;
-
-        DelegatingRunnable( Runnable delegate, Executor executor ) {
-            this.delegate = delegate;
-            this.executor = executor;
-        }
-
-        @Override
-        public void run() {
-            if( ON_TASK_COMPLETION_THREAD_TL.get() == null ) {
-                // We're not running on the task completion thread so run the delegate inline.
-                LOG.trace( "Executing ListenenableFuture Runnable on this thread: {}",
-                        Thread.currentThread().getName() );
-                delegate.run();
-            } else {
-                // We're running on the task completion thread so off-load to the executor.
-                LOG.trace( "Submitting ListenenableFuture Runnable to the listenerExecutor",
-                        Thread.currentThread().getName() );
-                executor.execute( delegate );
-            }
+            b.reset();
         }
     }
 }
index ab010c964de2a7ec32a12c7ef40458068ede6c5e..c44864350e863dc639e307d23fc343efbc38bfc1 100644 (file)
@@ -8,14 +8,14 @@
 
 package org.opendaylight.yangtools.util.concurrent;
 
+import com.google.common.base.Preconditions;
+
 import java.util.concurrent.RejectedExecutionHandler;
 import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicLongFieldUpdater;
 
 import org.opendaylight.yangtools.util.ExecutorServiceUtil;
 
-import com.google.common.base.Preconditions;
-
 /**
  * A RejectedExecutionHandler that delegates to a backing RejectedExecutionHandler and counts the
  * number of rejected tasks.
@@ -23,30 +23,31 @@ import com.google.common.base.Preconditions;
  * @author Thomas Pantelis
  */
 public class CountingRejectedExecutionHandler implements RejectedExecutionHandler {
-
+    private static final AtomicLongFieldUpdater<CountingRejectedExecutionHandler> COUNTER_UPDATER =
+            AtomicLongFieldUpdater.newUpdater(CountingRejectedExecutionHandler.class, "rejectedTaskCounter");
     private final RejectedExecutionHandler delegate;
-    private final AtomicLong rejectedTaskCounter = new AtomicLong();
+    private volatile long rejectedTaskCounter;
 
     /**
      * Constructor.
      *
      * @param delegate the backing RejectedExecutionHandler.
      */
-    public CountingRejectedExecutionHandler( RejectedExecutionHandler delegate ) {
+    public CountingRejectedExecutionHandler( final RejectedExecutionHandler delegate ) {
         this.delegate = Preconditions.checkNotNull( delegate );
     }
 
     @Override
-    public void rejectedExecution( Runnable task, ThreadPoolExecutor executor ) {
-        rejectedTaskCounter.incrementAndGet();
+    public void rejectedExecution( final Runnable task, final ThreadPoolExecutor executor ) {
+        COUNTER_UPDATER.incrementAndGet(this);
         delegate.rejectedExecution( task, executor );
     }
 
     /**
      * Returns the rejected task count.
      */
-    public long getRejectedTaskCount(){
-        return rejectedTaskCounter.get();
+    public long getRejectedTaskCount() {
+        return rejectedTaskCounter;
     }
 
     /**
index 011872d6b138d9edbaf28869524121bd859f0b43..958f2ee5118b265ccdde95cd8ed9891fdde373bb 100644 (file)
@@ -8,11 +8,12 @@
 
 package org.opendaylight.yangtools.util.concurrent;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-
 import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Supplier;
 import com.google.common.util.concurrent.ForwardingListenableFuture;
 import com.google.common.util.concurrent.ListenableFuture;
+
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
@@ -20,6 +21,7 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
+import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 /**
@@ -42,11 +44,35 @@ import javax.annotation.Nullable;
  * from this class override the <code>get</code> methods to check if the ThreadLocal is set. If it is,
  * an ExecutionException is thrown with a custom cause.
  *
+ * Note that the ThreadLocal is not removed automatically, so some state may be left hanging off of
+ * threads which have encountered this class. If you need to clean that state up, use
+ * {@link #cleanStateForCurrentThread()}.
+ *
  * @author Thomas Pantelis
+ * @author Robert Varga
  */
 public class DeadlockDetectingListeningExecutorService extends AsyncNotifyingListeningExecutorService {
-    private final ThreadLocal<Boolean> deadlockDetector = new ThreadLocal<>();
-    private final Function<Void, Exception> deadlockExceptionFunction;
+    /*
+     * We cannot use a static field simply because our API contract allows nesting, which means some
+     * tasks may be submitted to underlay and some to overlay service -- and the two cases need to
+     * be discerned reliably.
+     */
+    private final SettableBooleanThreadLocal deadlockDetector = new SettableBooleanThreadLocal();
+    private final Supplier<Exception> deadlockExceptionFunction;
+
+    // Compatibility wrapper, needs to be removed once the deprecated constructors are gone.
+    private static final class CompatExceptionSupplier implements Supplier<Exception> {
+        private final Function<Void, Exception> function;
+
+        private CompatExceptionSupplier(final Function<Void, Exception> function) {
+            this.function = Preconditions.checkNotNull(function);
+        }
+
+        @Override
+        public Exception get() {
+            return function.apply(null);
+        }
+    }
 
     /**
      * Constructor.
@@ -54,9 +80,11 @@ public class DeadlockDetectingListeningExecutorService extends AsyncNotifyingLis
      * @param delegate the backing ExecutorService.
      * @param deadlockExceptionFunction Function that returns an Exception instance to set as the
      *             cause of the ExecutionException when a deadlock is detected.
+     * @deprecated Use {@link #DeadlockDetectingListeningExecutorService(ExecutorService, Supplier)} instead.
      */
-    public DeadlockDetectingListeningExecutorService( ExecutorService delegate,
-                                          Function<Void,Exception> deadlockExceptionFunction ) {
+    @Deprecated
+    public DeadlockDetectingListeningExecutorService(final ExecutorService delegate,
+            final Function<Void, Exception> deadlockExceptionFunction) {
         this(delegate, deadlockExceptionFunction, null);
     }
 
@@ -68,43 +96,88 @@ public class DeadlockDetectingListeningExecutorService extends AsyncNotifyingLis
      *             cause of the ExecutionException when a deadlock is detected.
      * @param listenableFutureExecutor the executor used to run listener callbacks asynchronously.
      *             If null, no executor is used.
+     * @deprecated Use {@link #DeadlockDetectingListeningExecutorService(ExecutorService, Supplier, Executor)} instead.
      */
-    public DeadlockDetectingListeningExecutorService( ExecutorService delegate,
-                                          Function<Void,Exception> deadlockExceptionFunction,
-                                          @Nullable Executor listenableFutureExecutor ) {
+    @Deprecated
+    public DeadlockDetectingListeningExecutorService(final ExecutorService delegate,
+            final Function<Void, Exception> deadlockExceptionFunction,
+            @Nullable final Executor listenableFutureExecutor) {
         super(delegate, listenableFutureExecutor);
-        this.deadlockExceptionFunction = checkNotNull(deadlockExceptionFunction);
+        this.deadlockExceptionFunction = new CompatExceptionSupplier(deadlockExceptionFunction);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param delegate the backing ExecutorService.
+     * @param deadlockExceptionSupplier Supplier that returns an Exception instance to set as the
+     *             cause of the ExecutionException when a deadlock is detected.
+     */
+    public DeadlockDetectingListeningExecutorService(final ExecutorService delegate,
+            @Nonnull final Supplier<Exception> deadlockExceptionSupplier) {
+        this(delegate, deadlockExceptionSupplier, null);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param delegate the backing ExecutorService.
+     * @param deadlockExceptionSupplier Supplier that returns an Exception instance to set as the
+     *             cause of the ExecutionException when a deadlock is detected.
+     * @param listenableFutureExecutor the executor used to run listener callbacks asynchronously.
+     *             If null, no executor is used.
+     */
+    public DeadlockDetectingListeningExecutorService(final ExecutorService delegate,
+            @Nonnull final Supplier<Exception> deadlockExceptionSupplier,
+            @Nullable final Executor listenableFutureExecutor ) {
+        super(delegate, listenableFutureExecutor);
+        this.deadlockExceptionFunction = Preconditions.checkNotNull(deadlockExceptionSupplier);
     }
 
     @Override
-    public void execute( Runnable command ){
+    public void execute(final Runnable command) {
         getDelegate().execute(wrapRunnable(command));
     }
 
     @Override
-    public <T> ListenableFuture<T> submit( Callable<T> task ){
+    public <T> ListenableFuture<T> submit(final Callable<T> task) {
         return wrapListenableFuture(super.submit(wrapCallable(task)));
     }
 
     @Override
-    public ListenableFuture<?> submit( Runnable task ){
+    public ListenableFuture<?> submit(final Runnable task) {
         return wrapListenableFuture(super.submit(wrapRunnable(task)));
     }
 
     @Override
-    public <T> ListenableFuture<T> submit( Runnable task, T result ){
+    public <T> ListenableFuture<T> submit(final Runnable task, final T result) {
         return wrapListenableFuture(super.submit(wrapRunnable(task), result));
     }
 
+    /**
+     * Remove the state this instance may have attached to the calling thread. If no state
+     * was attached this method does nothing.
+     */
+    public void cleanStateForCurrentThread() {
+        deadlockDetector.remove();
+    }
+
+    private SettableBoolean primeDetector() {
+        final SettableBoolean b = deadlockDetector.get();
+        Preconditions.checkState(!b.isSet(), "Detector for {} has already been primed", this);
+        b.set();
+        return b;
+    }
+
     private Runnable wrapRunnable(final Runnable task) {
         return new Runnable() {
             @Override
             public void run() {
-                deadlockDetector.set(Boolean.TRUE);
+                final SettableBoolean b = primeDetector();
                 try {
                     task.run();
                 } finally {
-                    deadlockDetector.remove();
+                    b.reset();
                 }
             }
         };
@@ -114,17 +187,17 @@ public class DeadlockDetectingListeningExecutorService extends AsyncNotifyingLis
         return new Callable<T>() {
             @Override
             public T call() throws Exception {
-                deadlockDetector.set(Boolean.TRUE);
+                final SettableBoolean b = primeDetector();
                 try {
                     return delagate.call();
                 } finally {
-                    deadlockDetector.remove();
+                    b.reset();
                 }
             }
         };
     }
 
-    private <T> ListenableFuture<T> wrapListenableFuture(final ListenableFuture<T> delegate ) {
+    private <T> ListenableFuture<T> wrapListenableFuture(final ListenableFuture<T> delegate) {
         /*
          * This creates a forwarding Future that overrides calls to get(...) to check, via the
          * ThreadLocal, if the caller is doing a blocking call on a thread from this executor. If
@@ -148,9 +221,9 @@ public class DeadlockDetectingListeningExecutorService extends AsyncNotifyingLis
             }
 
             void checkDeadLockDetectorTL() throws ExecutionException {
-                if (deadlockDetector.get() != null) {
+                if (deadlockDetector.get().isSet()) {
                     throw new ExecutionException("A potential deadlock was detected.",
-                            deadlockExceptionFunction.apply(null));
+                            deadlockExceptionFunction.get());
                 }
             }
         };
diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/SettableBoolean.java b/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/SettableBoolean.java
new file mode 100644 (file)
index 0000000..0d584af
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.util.concurrent;
+
+/**
+ * Simple container encapsulating a boolean flag, which can be toggled. It starts
+ * off in the reset state.
+ */
+final class SettableBoolean {
+    private boolean value = false;
+
+    /**
+     * Set the flag to its initial (false) state.
+     */
+    public void reset() {
+        value = false;
+    }
+
+    /**
+     * Set the flag.
+     */
+    public void set() {
+        value = true;
+    }
+
+    /**
+     * Query the flag.
+     *
+     * @return True if the flag has been set since instantiation or last {@link #reset()}.
+     */
+    public boolean isSet() {
+        return value;
+    }
+}
diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/SettableBooleanThreadLocal.java b/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/SettableBooleanThreadLocal.java
new file mode 100644 (file)
index 0000000..8826f99
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.util.concurrent;
+
+/**
+ * A reusable {@link ThreadLocal} which returns a {@link SettableBoolean}.
+ */
+final class SettableBooleanThreadLocal extends ThreadLocal<SettableBoolean> {
+    @Override
+    protected SettableBoolean initialValue() {
+        return new SettableBoolean();
+    }
+
+    @Override
+    public void set(final SettableBoolean value) {
+        throw new UnsupportedOperationException("Resetting the value is not supported");
+    }
+}
index 38b5d9017fd65968c8464d6dd7b999dc7f760320..853a0aae0ebeb1d08f7145af5b30a55fac2b2328 100644 (file)
@@ -8,11 +8,12 @@
 
 package org.opendaylight.yangtools.util.concurrent;
 
+import com.google.common.annotations.Beta;
+
 import java.util.Collection;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicLongFieldUpdater;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
 
 /**
  * A {@link LinkedBlockingQueue} that tracks the largest queue size for debugging.
@@ -22,17 +23,15 @@ import java.util.concurrent.atomic.AtomicLongFieldUpdater;
  * @param <E> the element t.ype
  */
 public class TrackingLinkedBlockingQueue<E> extends LinkedBlockingQueue<E> {
-
+    @SuppressWarnings("rawtypes")
+    private static final AtomicIntegerFieldUpdater<TrackingLinkedBlockingQueue> LARGEST_QUEUE_SIZE_UPDATER = AtomicIntegerFieldUpdater.newUpdater(TrackingLinkedBlockingQueue.class, "largestQueueSize");
     private static final long serialVersionUID = 1L;
 
     /**
      * Holds largestQueueSize, this long field should be only accessed
      * using {@value #LARGEST_QUEUE_SIZE_UPDATER}
      */
-    private volatile long largestQueueSize = 0;
-
-    @SuppressWarnings("rawtypes")
-    private static AtomicLongFieldUpdater<TrackingLinkedBlockingQueue> LARGEST_QUEUE_SIZE_UPDATER = AtomicLongFieldUpdater.newUpdater(TrackingLinkedBlockingQueue.class, "largestQueueSize");
+    private volatile int largestQueueSize = 0;
 
     /**
      * @see LinkedBlockingQueue#LinkedBlockingQueue
@@ -44,26 +43,29 @@ public class TrackingLinkedBlockingQueue<E> extends LinkedBlockingQueue<E> {
     /**
      * @see LinkedBlockingQueue#LinkedBlockingQueue(Collection)
      */
-    public TrackingLinkedBlockingQueue( Collection<? extends E> c ) {
+    public TrackingLinkedBlockingQueue( final Collection<? extends E> c ) {
         super(c);
     }
 
     /**
      * @see LinkedBlockingQueue#LinkedBlockingQueue(int)
      */
-    public TrackingLinkedBlockingQueue( int capacity ) {
+    public TrackingLinkedBlockingQueue( final int capacity ) {
         super(capacity);
     }
 
     /**
      * Returns the largest queue size.
+     *
+     * FIXME: the this return will be changed to int in a future release.
      */
-    public long getLargestQueueSize(){
+    @Beta
+    public long getLargestQueueSize() {
         return largestQueueSize;
     }
 
     @Override
-    public boolean offer( E e, long timeout, TimeUnit unit ) throws InterruptedException {
+    public boolean offer( final E e, final long timeout, final TimeUnit unit ) throws InterruptedException {
         if( super.offer( e, timeout, unit ) ) {
             updateLargestQueueSize();
             return true;
@@ -73,7 +75,7 @@ public class TrackingLinkedBlockingQueue<E> extends LinkedBlockingQueue<E> {
     }
 
     @Override
-    public boolean offer( E e ) {
+    public boolean offer( final E e ) {
         if( super.offer( e ) ) {
             updateLargestQueueSize();
             return true;
@@ -83,20 +85,20 @@ public class TrackingLinkedBlockingQueue<E> extends LinkedBlockingQueue<E> {
     }
 
     @Override
-    public void put( E e ) throws InterruptedException {
+    public void put( final E e ) throws InterruptedException {
         super.put( e );
         updateLargestQueueSize();
     }
 
     @Override
-    public boolean add( E e ) {
+    public boolean add( final E e ) {
         boolean result = super.add( e );
         updateLargestQueueSize();
         return result;
     }
 
     @Override
-    public boolean addAll( Collection<? extends E> c ) {
+    public boolean addAll( final Collection<? extends E> c ) {
         try {
             return super.addAll( c );
         } finally {
@@ -105,10 +107,11 @@ public class TrackingLinkedBlockingQueue<E> extends LinkedBlockingQueue<E> {
     }
 
     private void updateLargestQueueSize() {
-        long size = size();
-        long largest = largestQueueSize;
-        if( size > largest ) {
-            LARGEST_QUEUE_SIZE_UPDATER.compareAndSet(this, largest, size );
-        }
+        final int size = size();
+
+        int largest;
+        do {
+            largest = largestQueueSize;
+        } while (size > largest && !LARGEST_QUEUE_SIZE_UPDATER.compareAndSet(this, largest, size));
     }
 }
diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/AbstractJSONCodec.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/AbstractJSONCodec.java
new file mode 100644 (file)
index 0000000..9464653
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.codec.gson;
+
+import com.google.common.base.Preconditions;
+
+import org.opendaylight.yangtools.concepts.Codec;
+import org.opendaylight.yangtools.yang.data.api.codec.BooleanCodec;
+import org.opendaylight.yangtools.yang.data.api.codec.DecimalCodec;
+import org.opendaylight.yangtools.yang.data.api.codec.Int16Codec;
+import org.opendaylight.yangtools.yang.data.api.codec.Int32Codec;
+import org.opendaylight.yangtools.yang.data.api.codec.Int64Codec;
+import org.opendaylight.yangtools.yang.data.api.codec.Int8Codec;
+import org.opendaylight.yangtools.yang.data.api.codec.Uint16Codec;
+import org.opendaylight.yangtools.yang.data.api.codec.Uint32Codec;
+import org.opendaylight.yangtools.yang.data.api.codec.Uint64Codec;
+import org.opendaylight.yangtools.yang.data.api.codec.Uint8Codec;
+import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
+
+/**
+ * Abstract base implementation of {@link JSONCodec}, which wraps a {@link TypeDefinitionAwareCodec}.
+ *
+ * @param <T> Deserialized objec type
+ */
+abstract class AbstractJSONCodec<T> implements JSONCodec<T> {
+    private final Codec<String, T> codec;
+
+    protected AbstractJSONCodec(final Codec<String, T> codec) {
+        this.codec = Preconditions.checkNotNull(codec);
+    }
+
+    /**
+     * Create a proper JSONCodec based on the underlying codec type
+     * @param codec underlying codec
+     * @return A JSONCodec instance
+     */
+    public static <T> JSONCodec<T> create(final Codec<String, T> codec) {
+        if (codec instanceof BooleanCodec || codec instanceof DecimalCodec ||
+                codec instanceof Int8Codec || codec instanceof Int16Codec ||
+                codec instanceof Int32Codec || codec instanceof Int64Codec ||
+                codec instanceof Uint8Codec || codec instanceof Uint16Codec ||
+                codec instanceof Uint32Codec || codec instanceof Uint64Codec) {
+            return new UnquotedJSONCodec<>(codec);
+        }
+
+        return new QuotedJSONCodec<>(codec);
+    }
+
+    @Override
+    public final T deserialize(final String input) {
+        return codec.deserialize(input);
+    }
+
+    @Override
+    public final String serialize(final T input) {
+        return codec.serialize(input);
+    }
+}
diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONCodec.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONCodec.java
new file mode 100644 (file)
index 0000000..aa52259
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.codec.gson;
+
+import org.opendaylight.yangtools.concepts.Codec;
+
+interface JSONCodec<T> extends Codec<String, T> {
+    boolean needQuotes();
+}
similarity index 55%
rename from yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/CodecFactory.java
rename to yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONCodecFactory.java
index dba5ed7d24638c4f757622911725a75ff8d84ff9..8ee9517ec2db90f976aa64e5b26a883abe3a7c4e 100644 (file)
@@ -8,12 +8,11 @@
 package org.opendaylight.yangtools.yang.data.codec.gson;
 
 import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 
-import org.opendaylight.yangtools.concepts.Codec;
-import org.opendaylight.yangtools.yang.data.api.codec.LeafrefCodec;
 import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
@@ -24,35 +23,30 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * This class is implementation-internal and subject to change. Please do not use it.
+ * Factory for creating JSON equivalents of codecs. Each instance of this object is bound to
+ * a particular {@link SchemaContext}, but can be reused by multiple {@link JSONNormalizedNodeStreamWriter}s.
  */
 @Beta
-final class CodecFactory {
-    private static final Logger LOG = LoggerFactory.getLogger(CodecFactory.class);
-    private static final Codec<?, ?> LEAFREF_DEFAULT_CODEC = new LeafrefCodec<String>() {
+public final class JSONCodecFactory {
+    private static final Logger LOG = LoggerFactory.getLogger(JSONCodecFactory.class);
+    private static final JSONCodec<Object> LEAFREF_DEFAULT_CODEC = new JSONLeafrefCodec();
+    private static final JSONCodec<Object> NULL_CODEC = new JSONCodec<Object>() {
         @Override
-        public String serialize(final Object data) {
-            return String.valueOf(data);
+        public Object deserialize(final String input) {
+            return null;
         }
 
         @Override
-        public Object deserialize(final String data) {
-            return data;
-        }
-    };
-    private static final Codec<?, ?> NULL_CODEC = new Codec<Object, Object>() {
-        @Override
-        public Object deserialize(final Object input) {
+        public String serialize(final Object input) {
             return null;
         }
 
         @Override
-        public Object serialize(final Object input) {
-            return null;
+        public boolean needQuotes() {
+            return false;
         }
     };
 
-
     private static TypeDefinition<?> resolveBaseTypeFrom(final TypeDefinition<?> type) {
         TypeDefinition<?> superType = type;
         while (superType.getBaseType() != null) {
@@ -61,17 +55,18 @@ final class CodecFactory {
         return superType;
     }
 
-    private final LoadingCache<TypeDefinition<?>, Codec<?, ?>> codecs =
-            CacheBuilder.newBuilder().softValues().build(new CacheLoader<TypeDefinition<?>, Codec<?, ?>>() {
+    private final LoadingCache<TypeDefinition<?>, JSONCodec<Object>> codecs =
+            CacheBuilder.newBuilder().softValues().build(new CacheLoader<TypeDefinition<?>, JSONCodec<Object>>() {
+        @SuppressWarnings("unchecked")
         @Override
-        public Codec<?, ?> load(final TypeDefinition<?> key) throws Exception {
+        public JSONCodec<Object> load(final TypeDefinition<?> key) throws Exception {
             final TypeDefinition<?> type = resolveBaseTypeFrom(key);
 
             if (type instanceof InstanceIdentifierType) {
-                return iidCodec;
+                return (JSONCodec<Object>) iidCodec;
             }
             if (type instanceof IdentityrefType) {
-                return idrefCodec;
+                return (JSONCodec<Object>) idrefCodec;
             }
             if (type instanceof LeafrefTypeDefinition) {
                 return LEAFREF_DEFAULT_CODEC;
@@ -83,24 +78,35 @@ final class CodecFactory {
                 return NULL_CODEC;
             }
 
-            return codec;
+            return AbstractJSONCodec.create(codec);
         }
     });
 
-    private final Codec<?, ?> iidCodec;
-    private final Codec<?, ?> idrefCodec;
+    private final SchemaContext schemaContext;
+    private final JSONCodec<?> iidCodec;
+    private final JSONCodec<?> idrefCodec;
 
-    private CodecFactory(final SchemaContext context) {
+    private JSONCodecFactory(final SchemaContext context) {
+        this.schemaContext = Preconditions.checkNotNull(context);
         iidCodec = new JSONStringInstanceIdentifierCodec(context);
         idrefCodec = new JSONStringIdentityrefCodec(context);
     }
 
-    public static CodecFactory create(final SchemaContext context) {
-        return new CodecFactory(context);
+    /**
+     * Instantiate a new codec factory attached to a particular context.
+     *
+     * @param context SchemaContext to which the factory should be bound
+     * @return A codec factory instance.
+     */
+    public static JSONCodecFactory create(final SchemaContext context) {
+        return new JSONCodecFactory(context);
+    }
+
+    SchemaContext getSchemaContext() {
+        return schemaContext;
     }
 
-    @SuppressWarnings("unchecked")
-    public final Codec<Object, Object> codecFor(final TypeDefinition<?> typeDefinition) {
-        return (Codec<Object, Object>) codecs.getUnchecked(typeDefinition);
+    JSONCodec<Object> codecFor(final TypeDefinition<?> typeDefinition) {
+        return codecs.getUnchecked(typeDefinition);
     }
 }
diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONLeafrefCodec.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONLeafrefCodec.java
new file mode 100644 (file)
index 0000000..5613433
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.codec.gson;
+
+import org.opendaylight.yangtools.yang.data.api.codec.LeafrefCodec;
+
+final class JSONLeafrefCodec implements JSONCodec<Object>, LeafrefCodec<String> {
+    @Override
+    public Object deserialize(final String input) {
+        return input;
+    }
+
+    @Override
+    public String serialize(final Object input) {
+        return String.valueOf(input);
+    }
+
+    @Override
+    public boolean needQuotes() {
+        return true;
+    }
+}
\ No newline at end of file
index c7755b83bd42ac282f309014ff4d20963a4873e3..058ad5b78e8cb95729dbf90025ed4b3cdcd87e45 100644 (file)
@@ -9,28 +9,21 @@ package org.opendaylight.yangtools.yang.data.codec.gson;
 
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableSet;
 import com.google.gson.stream.JsonWriter;
 
 import java.io.IOException;
 import java.io.Writer;
-import java.math.BigDecimal;
-import java.math.BigInteger;
 import java.net.URI;
-import java.util.ArrayDeque;
-import java.util.Collection;
-import java.util.Deque;
 
-import org.opendaylight.yangtools.concepts.Codec;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.impl.codec.SchemaTracker;
 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 
@@ -42,60 +35,20 @@ import org.opendaylight.yangtools.yang.model.api.SchemaPath;
  * FIXME: rewrite this in terms of {@link JsonWriter}.
  */
 public class JSONNormalizedNodeStreamWriter implements NormalizedNodeStreamWriter {
+    /**
+     * RFC6020 deviation: we are not required to emit empty containers unless they
+     * are marked as 'presence'.
+     */
+    private static final boolean DEFAULT_EMIT_EMPTY_CONTAINERS = true;
 
-    private static enum NodeType {
-        OBJECT,
-        LIST,
-        OTHER,
-    }
-
-    private static class TypeInfo {
-        private boolean hasAtLeastOneChild = false;
-        private final NodeType type;
-        private final URI uri;
-
-        public TypeInfo(final NodeType type, final URI uri) {
-            this.type = type;
-            this.uri = uri;
-        }
-
-        public void setHasAtLeastOneChild(final boolean hasChildren) {
-            this.hasAtLeastOneChild = hasChildren;
-        }
-
-        public NodeType getType() {
-            return type;
-        }
-
-        public URI getNamespace() {
-            return uri;
-        }
-
-        public boolean hasAtLeastOneChild() {
-            return hasAtLeastOneChild;
-        }
-    }
-
-    private static final Collection<Class<?>> NUMERIC_CLASSES =
-            ImmutableSet.<Class<?>>of(Byte.class, Short.class, Integer.class, Long.class, BigInteger.class, BigDecimal.class);
-    private final Deque<TypeInfo> stack = new ArrayDeque<>();
-    private final SchemaContext schemaContext;
-    private final CodecFactory codecs;
     private final SchemaTracker tracker;
+    private final JSONCodecFactory codecs;
     private final Writer writer;
     private final String indent;
+    private JSONStreamWriterContext context;
 
-    private int currentDepth = 0;
-    private URI currentNamespace;
-
-    private JSONNormalizedNodeStreamWriter(final SchemaContext schemaContext,
-            final Writer writer, final int indentSize) {
-        this(schemaContext, SchemaPath.ROOT, writer, null, indentSize);
-    }
-
-    private JSONNormalizedNodeStreamWriter(final SchemaContext schemaContext, final SchemaPath path,
+    private JSONNormalizedNodeStreamWriter(final JSONCodecFactory codecFactory, final SchemaPath path,
             final Writer writer, final URI initialNs, final int indentSize) {
-        this.schemaContext = Preconditions.checkNotNull(schemaContext);
         this.writer = Preconditions.checkNotNull(writer);
 
         Preconditions.checkArgument(indentSize >= 0, "Indent size must be non-negative");
@@ -104,10 +57,9 @@ public class JSONNormalizedNodeStreamWriter implements NormalizedNodeStreamWrite
         } else {
             indent = null;
         }
-        this.codecs = CodecFactory.create(schemaContext);
-        this.tracker = SchemaTracker.create(schemaContext, path);
-
-        this.currentNamespace = initialNs;
+        this.codecs = Preconditions.checkNotNull(codecFactory);
+        this.tracker = SchemaTracker.create(codecFactory.getSchemaContext(), path);
+        this.context = new JSONStreamWriterRootContext(initialNs);
     }
 
     /**
@@ -118,30 +70,33 @@ public class JSONNormalizedNodeStreamWriter implements NormalizedNodeStreamWrite
      * @return A stream writer instance
      */
     public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final Writer writer) {
-        return new JSONNormalizedNodeStreamWriter(schemaContext, writer, 0);
+        return new JSONNormalizedNodeStreamWriter(JSONCodecFactory.create(schemaContext), SchemaPath.ROOT, writer, null, 0);
     }
 
     /**
      * Create a new stream writer, which writes to the specified {@link Writer}.
      *
      * @param schemaContext Schema context
+     * @param path Root schemapath
      * @param writer Output writer
      * @return A stream writer instance
      */
-    public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final SchemaPath path,final Writer writer) {
-        return new JSONNormalizedNodeStreamWriter(schemaContext, path, writer, null, 0);
+    public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final SchemaPath path, final Writer writer) {
+        return new JSONNormalizedNodeStreamWriter(JSONCodecFactory.create(schemaContext), path, writer, null, 0);
     }
 
     /**
      * Create a new stream writer, which writes to the specified {@link Writer}.
      *
      * @param schemaContext Schema context
+     * @param path Root schemapath
      * @param writer Output writer
      * @param initialNs Initial namespace
      * @return A stream writer instance
      */
-    public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final SchemaPath path,final URI initialNs, final Writer writer) {
-        return new JSONNormalizedNodeStreamWriter(schemaContext, path, writer, initialNs, 0);
+    public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final SchemaPath path,
+            final URI initialNs, final Writer writer) {
+        return new JSONNormalizedNodeStreamWriter(JSONCodecFactory.create(schemaContext), path, writer, initialNs, 0);
     }
 
     /**
@@ -153,118 +108,99 @@ public class JSONNormalizedNodeStreamWriter implements NormalizedNodeStreamWrite
      * @return A stream writer instance
      */
     public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final Writer writer, final int indentSize) {
-        return new JSONNormalizedNodeStreamWriter(schemaContext, writer, indentSize);
+        return new JSONNormalizedNodeStreamWriter(JSONCodecFactory.create(schemaContext), SchemaPath.ROOT, writer, null, indentSize);
+    }
+
+    /**
+     * Create a new stream writer, which writes to the specified output stream. The codec factory
+     * can be reused between multiple writers.
+     *
+     * @param codecFactor JSON codec factory
+     * @param writer Output writer
+     * @param indentSize indentation size
+     * @return A stream writer instance
+     */
+    public static NormalizedNodeStreamWriter create(final JSONCodecFactory codecFactory, final Writer writer, final int indentSize) {
+        return new JSONNormalizedNodeStreamWriter(codecFactory, SchemaPath.ROOT, writer, null, indentSize);
     }
 
     @Override
     public void leafNode(final NodeIdentifier name, final Object value) throws IOException {
         final LeafSchemaNode schema = tracker.leafNode(name);
-        final Codec<Object, Object> codec = codecs.codecFor(schema.getType());
+        final JSONCodec<Object> codec = codecs.codecFor(schema.getType());
 
-        separateElementFromPreviousElement();
-        writeJsonIdentifier(name);
-        currentNamespace = stack.peek().getNamespace();
-        writeValue(codec.serialize(value));
-        separateNextSiblingsWithComma();
+        context.emittingChild(codecs.getSchemaContext(), writer, indent);
+        context.writeJsonIdentifier(codecs.getSchemaContext(), writer, name.getNodeType());
+        writeValue(codec.serialize(value), codec.needQuotes());
     }
 
     @Override
     public void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
         tracker.startLeafSet(name);
-
-        separateElementFromPreviousElement();
-        stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace()));
-        writeJsonIdentifier(name);
-        writeStartList();
-        indentRight();
+        context = new JSONStreamWriterListContext(context, name);
     }
 
     @Override
     public void leafSetEntryNode(final Object value) throws IOException {
         final LeafListSchemaNode schema = tracker.leafSetEntryNode();
-        final Codec<Object, Object> codec = codecs.codecFor(schema.getType());
+        final JSONCodec<Object> codec = codecs.codecFor(schema.getType());
 
-        separateElementFromPreviousElement();
-        writeValue(codec.serialize(value));
-        separateNextSiblingsWithComma();
+        context.emittingChild(codecs.getSchemaContext(), writer, indent);
+        writeValue(codec.serialize(value), codec.needQuotes());
     }
 
+    /*
+     * Warning suppressed due to static final constant which triggers a warning
+     * for the call to schema.isPresenceContainer().
+     */
+    @SuppressWarnings("unused")
     @Override
     public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
-        tracker.startContainerNode(name);
-
-        separateElementFromPreviousElement();
-        stack.push(new TypeInfo(NodeType.OBJECT, name.getNodeType().getNamespace()));
-        writeJsonIdentifier(name);
-        writeStartObject();
-        indentRight();
+        final ContainerSchemaNode schema = tracker.startContainerNode(name);
+        context = new JSONStreamWriterNamedObjectContext(context, name, DEFAULT_EMIT_EMPTY_CONTAINERS || schema.isPresenceContainer());
     }
 
     @Override
     public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
         tracker.startList(name);
-
-        separateElementFromPreviousElement();
-        stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace()));
-        writeJsonIdentifier(name);
-        writeStartList();
-        indentRight();
+        context = new JSONStreamWriterListContext(context, name);
     }
 
     @Override
     public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
         tracker.startListItem(name);
-
-        separateElementFromPreviousElement();
-        stack.push(new TypeInfo(NodeType.OBJECT, name.getNodeType().getNamespace()));
-        writeStartObject();
-        indentRight();
+        context = new JSONStreamWriterObjectContext(context, name, DEFAULT_EMIT_EMPTY_CONTAINERS);
     }
 
     @Override
     public void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
         tracker.startList(name);
-
-        separateElementFromPreviousElement();
-        stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace()));
-        writeJsonIdentifier(name);
-        writeStartList();
-        indentRight();
+        context = new JSONStreamWriterListContext(context, name);
     }
 
     @Override
     public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
             throws IOException {
         tracker.startListItem(identifier);
-        separateElementFromPreviousElement();
-        stack.push(new TypeInfo(NodeType.OBJECT, identifier.getNodeType().getNamespace()));
-
-
-        writeStartObject();
-        indentRight();
+        context = new JSONStreamWriterObjectContext(context, identifier, DEFAULT_EMIT_EMPTY_CONTAINERS);
     }
 
     @Override
     public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
         tracker.startList(name);
-
-        separateElementFromPreviousElement();
-        stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace()));
-        writeJsonIdentifier(name);
-        writeStartList();
-        indentRight();
+        context = new JSONStreamWriterListContext(context, name);
     }
 
     @Override
-    public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IllegalArgumentException {
+    public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) {
         tracker.startChoiceNode(name);
-        handleInvisibleNode(name.getNodeType().getNamespace());
+        context = new JSONStreamWriterInvisibleContext(context);
     }
 
     @Override
-    public void startAugmentationNode(final AugmentationIdentifier identifier) throws IllegalArgumentException {
+    public void startAugmentationNode(final AugmentationIdentifier identifier) {
         tracker.startAugmentationNode(identifier);
-        handleInvisibleNode(currentNamespace);
+        context = new JSONStreamWriterInvisibleContext(context);
     }
 
     @Override
@@ -272,91 +208,19 @@ public class JSONNormalizedNodeStreamWriter implements NormalizedNodeStreamWrite
         final AnyXmlSchemaNode schema = tracker.anyxmlNode(name);
         // FIXME: should have a codec based on this :)
 
-        separateElementFromPreviousElement();
-        writeJsonIdentifier(name);
-        currentNamespace = stack.peek().getNamespace();
-        writeValue(value);
-        separateNextSiblingsWithComma();
+        context.emittingChild(codecs.getSchemaContext(), writer, indent);
+        context.writeJsonIdentifier(codecs.getSchemaContext(), writer, name.getNodeType());
+        writeValue(String.valueOf(value), true);
     }
 
     @Override
     public void endNode() throws IOException {
         tracker.endNode();
-
-        final TypeInfo t = stack.pop();
-        switch (t.getType()) {
-        case LIST:
-            indentLeft();
-            newLine();
-            writer.append(']');
-            break;
-        case OBJECT:
-            indentLeft();
-            newLine();
-            writer.append('}');
-            break;
-        default:
-            break;
-        }
-
-        currentNamespace = stack.isEmpty() ? null : stack.peek().getNamespace();
-        separateNextSiblingsWithComma();
-    }
-
-    private void separateElementFromPreviousElement() throws IOException {
-        if (!stack.isEmpty() && stack.peek().hasAtLeastOneChild()) {
-            writer.append(',');
-        }
-        newLine();
-    }
-
-    private void newLine() throws IOException {
-        if (indent != null) {
-            writer.append('\n');
-
-            for (int i = 0; i < currentDepth; i++) {
-                writer.append(indent);
-            }
-        }
-    }
-
-    private void separateNextSiblingsWithComma() {
-        if (!stack.isEmpty()) {
-            stack.peek().setHasAtLeastOneChild(true);
-        }
-    }
-
-    /**
-     * Invisible nodes have to be also pushed to stack because of pairing of start*() and endNode() methods. Information
-     * about child existing (due to printing comma) has to be transfered to invisible node.
-     */
-    private void handleInvisibleNode(final URI uri) {
-        TypeInfo typeInfo = new TypeInfo(NodeType.OTHER, uri);
-        typeInfo.setHasAtLeastOneChild(stack.peek().hasAtLeastOneChild());
-        stack.push(typeInfo);
-    }
-
-    private void writeStartObject() throws IOException {
-        writer.append('{');
+        context = context.endNode(codecs.getSchemaContext(), writer, indent);
     }
 
-    private void writeStartList() throws IOException {
-        writer.append('[');
-    }
-
-    private void writeModulName(final URI namespace) throws IOException {
-        if (this.currentNamespace == null || namespace != this.currentNamespace) {
-            Module module = schemaContext.findModuleByNamespaceAndRevision(namespace, null);
-            writer.append(module.getName());
-            writer.append(':');
-            currentNamespace = namespace;
-        }
-    }
-
-    private void writeValue(final Object value) throws IOException {
-        final String str = String.valueOf(value);
-
-        if (!NUMERIC_CLASSES.contains(value.getClass())) {
+    private void writeValue(final String str, final boolean needQuotes) throws IOException {
+        if (needQuotes) {
             writer.append('"');
             writer.append(str);
             writer.append('"');
@@ -365,21 +229,6 @@ public class JSONNormalizedNodeStreamWriter implements NormalizedNodeStreamWrite
         }
     }
 
-    private void writeJsonIdentifier(final NodeIdentifier name) throws IOException {
-        writer.append('"');
-        writeModulName(name.getNodeType().getNamespace());
-        writer.append(name.getNodeType().getLocalName());
-        writer.append("\":");
-    }
-
-    private void indentRight() {
-        currentDepth++;
-    }
-
-    private void indentLeft() {
-        currentDepth--;
-    }
-
     @Override
     public void flush() throws IOException {
         writer.flush();
diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterContext.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterContext.java
new file mode 100644 (file)
index 0000000..1a5181a
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.codec.gson;
+
+import com.google.common.base.Preconditions;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.net.URI;
+
+import javax.annotation.Nonnull;
+
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Abstract base class for a single level of {@link JSONNormalizedNodeStreamWriter}
+ * recursion. Provides the base API towards the writer, which is then specialized
+ * by subclasses.
+ */
+abstract class JSONStreamWriterContext {
+    private final JSONStreamWriterContext parent;
+    private final boolean mandatory;
+    private final int depth;
+    private boolean emittedMyself = false;
+    private boolean haveChild = false;
+
+    /**
+     * Construct a new context.
+     *
+     * @param parent Parent context, usually non-null.
+     * @param mandatory Mandatory flag. If set to true, the corresponding node
+     *                  will be emitted even if it has no children.
+     */
+    protected JSONStreamWriterContext(final JSONStreamWriterContext parent, final boolean mandatory) {
+        this.mandatory = mandatory;
+        this.parent = parent;
+
+        if (parent != null) {
+            depth = parent.depth + 1;
+        } else {
+            depth = 0;
+        }
+    }
+
+    /**
+     * Write a JSON node identifier, optionally prefixing it with the module name
+     * corresponding to its namespace.
+     *
+     * @param schema Schema context
+     * @param writer Output writer
+     * @param qname Namespace/name tuple
+     * @throws IOException when the writer reports it
+     */
+    protected final void writeJsonIdentifier(final SchemaContext schema, final Writer writer, final QName qname) throws IOException {
+        writer.append('"');
+
+        // Prepend module name if namespaces do not match
+        final URI ns = qname.getNamespace();
+        if (!ns.equals(getNamespace())) {
+            final Module module = schema.findModuleByNamespaceAndRevision(ns, null);
+            Preconditions.checkArgument(module != null, "Could not find module for namespace {}", ns);
+
+            writer.append(module.getName());
+            writer.append(':');
+        }
+
+        writer.append(qname.getLocalName());
+        writer.append("\":");
+    }
+
+    /**
+     * Return the namespace associated with current node.
+     *
+     * @return Namespace as URI
+     */
+    protected abstract @Nonnull URI getNamespace();
+
+    /**
+     * Emit the start of an element.
+     *
+     * @param schema Schema context
+     * @param writer Output writer
+     * @throws IOException
+     */
+    protected abstract void emitStart(final SchemaContext schema, final Writer writer) throws IOException;
+
+    /**
+     * Emit the end of an element.
+     *
+     * @param schema Schema context
+     * @param writer Output writer
+     * @throws IOException
+     */
+    protected abstract void emitEnd(final Writer writer) throws IOException;
+
+    private final void emitMyself(final SchemaContext schema, final Writer writer, final String indent) throws IOException {
+        if (!emittedMyself) {
+            if (parent != null) {
+                parent.emittingChild(schema, writer, indent);
+            }
+
+            emitStart(schema, writer);
+            emittedMyself = true;
+        }
+    }
+
+    /**
+     * Invoked whenever a child node is being emitted. Checks whether this node has
+     * been emitted, and takes care of that if necessary. Also makes sure separator
+     * is emitted before a second and subsequent child.
+     *
+     * @param schema Schema context
+     * @param writer Output writer
+     * @param indent Indentation string
+     * @throws IOException when writer reports it
+     */
+    final void emittingChild(final SchemaContext schema, final Writer writer, final String indent) throws IOException {
+        emitMyself(schema, writer, indent);
+        if (haveChild) {
+            writer.append(',');
+        }
+
+        if (indent != null) {
+            writer.append('\n');
+
+            for (int i = 0; i < depth; i++) {
+                writer.append(indent);
+            }
+        }
+        haveChild = true;
+    }
+
+    /**
+     * Invoked by the writer when it is leaving this node. Checks whether this node
+     * needs to be emitted and takes of that if necessary.
+     *
+     * @param schema Schema context
+     * @param writer Output writer
+     * @param indent Indentation string
+     * @return Parent node context
+     * @throws IOException when writer reports it
+     * @throws IllegalArgumentException if this node cannot be ended (e.g. root)
+     */
+    final JSONStreamWriterContext endNode(final SchemaContext schema, final Writer writer, final String indent) throws IOException {
+        if (!emittedMyself && mandatory) {
+            emitMyself(schema, writer, indent);
+        }
+
+        if (emittedMyself) {
+            emitEnd(writer);
+        }
+        return parent;
+    }
+}
diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterInvisibleContext.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterInvisibleContext.java
new file mode 100644 (file)
index 0000000..7f22c19
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.codec.gson;
+
+import com.google.common.base.Preconditions;
+
+import java.io.Writer;
+
+/**
+ * A virtual recursion level in {@link JSONNormalizedNodeStreamWriter}, used for nodes
+ * which are not emitted in the JSON representation.
+ */
+final class JSONStreamWriterInvisibleContext extends JSONStreamWriterURIContext {
+    JSONStreamWriterInvisibleContext(final JSONStreamWriterContext parent) {
+        super(Preconditions.checkNotNull(parent), parent.getNamespace());
+    }
+
+    @Override
+    protected void emitEnd(final Writer writer) {
+        // No-op
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterListContext.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterListContext.java
new file mode 100644 (file)
index 0000000..ed7bd09
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.codec.gson;
+
+import com.google.common.base.Preconditions;
+import java.io.IOException;
+import java.io.Writer;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * A single recursion level of {@link JSONNormalizedNodeStreamWriter} representing
+ * a list.
+ */
+final class JSONStreamWriterListContext extends JSONStreamWriterQNameContext {
+    protected JSONStreamWriterListContext(final JSONStreamWriterContext parent, final NodeIdentifier id) {
+        super(Preconditions.checkNotNull(parent), id.getNodeType(), false);
+    }
+
+    @Override
+    protected void emitStart(final SchemaContext schema, final Writer writer) throws IOException {
+        writeJsonIdentifier(schema, writer, getQName());
+        writer.append('[');
+    }
+
+    @Override
+    protected void emitEnd(final Writer writer) throws IOException {
+        writer.append(']');
+    }
+}
diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterNamedObjectContext.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterNamedObjectContext.java
new file mode 100644 (file)
index 0000000..f5e5d48
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.codec.gson;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * A recursion level of {@link JSONNormalizedNodeStreamWriter}, which represents
+ * a JSON object which has to be prefixed with its identifier -- such as a
+ * container.
+ */
+final class JSONStreamWriterNamedObjectContext extends JSONStreamWriterObjectContext {
+    protected JSONStreamWriterNamedObjectContext(final JSONStreamWriterContext parent, final PathArgument arg, final boolean mandatory) {
+        super(parent, arg, mandatory);
+    }
+
+    @Override
+    protected void emitStart(final SchemaContext schema, final Writer writer) throws IOException {
+        writeJsonIdentifier(schema, writer, getQName());
+        super.emitStart(schema, writer);
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterObjectContext.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterObjectContext.java
new file mode 100644 (file)
index 0000000..d12f044
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.codec.gson;
+
+import com.google.common.base.Preconditions;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * A recursion level of {@link JSONNormalizedNodeStreamWriter}, which represents
+ * a JSON object which does not have to be prefixed with its identifier -- such
+ * as when it is in a containing list.
+ */
+class JSONStreamWriterObjectContext extends JSONStreamWriterQNameContext {
+    protected JSONStreamWriterObjectContext(final JSONStreamWriterContext parent, final PathArgument arg, final boolean mandatory) {
+        super(Preconditions.checkNotNull(parent), arg.getNodeType(), mandatory);
+    }
+
+    @Override
+    protected void emitStart(final SchemaContext schema, final Writer writer) throws IOException {
+        writer.append('{');
+    }
+
+    @Override
+    protected void emitEnd(final Writer writer) throws IOException {
+        writer.append('}');
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterQNameContext.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterQNameContext.java
new file mode 100644 (file)
index 0000000..ad4ad6b
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.codec.gson;
+
+import com.google.common.base.Preconditions;
+
+import java.net.URI;
+
+import org.opendaylight.yangtools.yang.common.QName;
+
+/**
+ * Abstract base class for {@link JSONNormalizedNodeStreamWriter} recursion
+ * levels which emit a QName-identified node.
+ */
+abstract class JSONStreamWriterQNameContext extends JSONStreamWriterContext {
+    private final QName qname;
+
+    protected JSONStreamWriterQNameContext(final JSONStreamWriterContext parent, final QName qname, final boolean mandatory) {
+        super(parent, mandatory);
+        this.qname = Preconditions.checkNotNull(qname);
+    }
+
+    /**
+     * Returns the node's identifier as a QName.
+     *
+     * @return QName identifier
+     */
+    protected final QName getQName() {
+        return qname;
+    }
+
+    @Override
+    protected final URI getNamespace() {
+        return qname.getNamespace();
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterRootContext.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterRootContext.java
new file mode 100644 (file)
index 0000000..36c3ff3
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.codec.gson;
+
+import java.io.Writer;
+import java.net.URI;
+
+/**
+ * The root node of a particular {@link JSONNormalizedNodeStreamWriter} instance.
+ * It holds the base namespace and can never be removed from the stack.
+ */
+final class JSONStreamWriterRootContext extends JSONStreamWriterURIContext {
+    JSONStreamWriterRootContext(final URI namespace) {
+        super(null, namespace);
+    }
+
+    @Override
+    protected void emitEnd(final Writer writer) {
+        throw new IllegalArgumentException("Top-level node reached");
+    }
+}
diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterURIContext.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStreamWriterURIContext.java
new file mode 100644 (file)
index 0000000..06c32bf
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.codec.gson;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.net.URI;
+
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Abstract class tracking a virtual level of {@link JSONNormalizedNodeStreamWriter}
+ * recursion. It only tracks the namespace associated with this node.
+ */
+abstract class JSONStreamWriterURIContext extends JSONStreamWriterContext {
+    private final URI namespace;
+
+    protected JSONStreamWriterURIContext(final JSONStreamWriterContext parent, final URI namespace) {
+        super(parent, false);
+        this.namespace = namespace;
+    }
+
+    @Override
+    protected final URI getNamespace() {
+        return namespace;
+    }
+
+    @Override
+    protected final void emitStart(final SchemaContext schema, final Writer writer) throws IOException {
+        // No-op
+    }
+}
\ No newline at end of file
index 66d55237a567c813d31b1d77d7e3505731ac17b8..fcbe473cca8dc6b414814a2e05ed3f92d9e3673e 100644 (file)
@@ -11,11 +11,12 @@ import com.google.common.base.Preconditions;
 
 import java.net.URI;
 
+import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.util.AbstractModuleStringIdentityrefCodec;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
-final class JSONStringIdentityrefCodec extends AbstractModuleStringIdentityrefCodec {
+final class JSONStringIdentityrefCodec extends AbstractModuleStringIdentityrefCodec implements JSONCodec<QName> {
     private final SchemaContext context;
 
     JSONStringIdentityrefCodec(final SchemaContext context) {
@@ -33,4 +34,8 @@ final class JSONStringIdentityrefCodec extends AbstractModuleStringIdentityrefCo
         return module == null ? null : module.getName();
     }
 
+    @Override
+    public boolean needQuotes() {
+        return true;
+    }
 }
index fc1322fba9096bc3a3e906855a630e748ae71514..a1580dd5bb2c3f5681569c1c3541e68d38f51cbf 100644 (file)
@@ -11,11 +11,12 @@ import com.google.common.base.Preconditions;
 
 import java.net.URI;
 
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.util.AbstractModuleStringInstanceIdentifierCodec;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
-final class JSONStringInstanceIdentifierCodec extends AbstractModuleStringInstanceIdentifierCodec {
+final class JSONStringInstanceIdentifierCodec extends AbstractModuleStringInstanceIdentifierCodec implements JSONCodec<YangInstanceIdentifier> {
     private final SchemaContext context;
 
     JSONStringInstanceIdentifierCodec(final SchemaContext context) {
@@ -32,4 +33,9 @@ final class JSONStringInstanceIdentifierCodec extends AbstractModuleStringInstan
         final Module module = context.findModuleByNamespaceAndRevision(namespace, null);
         return module == null ? null : module.getName();
     }
+
+    @Override
+    public boolean needQuotes() {
+        return true;
+    }
 }
index 65c234aae4b87dfef3c87fd3662cf34fd2c660d2..cea3fa683c02cee5c120370d5c680a325d1b5479 100644 (file)
@@ -50,13 +50,13 @@ import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
 public final class JsonParserStream implements Closeable, Flushable {
     private final Deque<URI> namespaces = new ArrayDeque<>();
     private final NormalizedNodeStreamWriter writer;
-    private final CodecFactory codecs;
+    private final JSONCodecFactory codecs;
     private final SchemaContext schema;
 
     private JsonParserStream(final NormalizedNodeStreamWriter writer, final SchemaContext schemaContext) {
         this.schema = Preconditions.checkNotNull(schemaContext);
         this.writer = Preconditions.checkNotNull(writer);
-        this.codecs = CodecFactory.create(schemaContext);
+        this.codecs = JSONCodecFactory.create(schemaContext);
     }
 
     public static JsonParserStream create(final NormalizedNodeStreamWriter writer, final SchemaContext schemaContext) {
diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/QuotedJSONCodec.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/QuotedJSONCodec.java
new file mode 100644 (file)
index 0000000..e8606f8
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.codec.gson;
+
+import org.opendaylight.yangtools.concepts.Codec;
+
+/**
+ * A {@link JSONCodec} which needs double quotes in output representation.
+ *
+ * @param <T> Deserialized value type
+ */
+final class QuotedJSONCodec<T> extends AbstractJSONCodec<T> {
+    QuotedJSONCodec(final Codec<String, T> codec) {
+        super(codec);
+    }
+
+    @Override
+    public boolean needQuotes() {
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/UnquotedJSONCodec.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/UnquotedJSONCodec.java
new file mode 100644 (file)
index 0000000..f29db5d
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.codec.gson;
+
+import org.opendaylight.yangtools.concepts.Codec;
+
+/**
+ * A {@link JSONCodec} which does not need double quotes in output representation.
+ *
+ * @param <T> Deserialized value type
+ */
+final class UnquotedJSONCodec<T> extends AbstractJSONCodec<T> {
+    UnquotedJSONCodec(final Codec<String, T> codec) {
+        super(codec);
+    }
+
+    @Override
+    public boolean needQuotes() {
+        return false;
+    }
+}
\ No newline at end of file