http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/activities/SignUpActivity.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/activities/SignUpActivity.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/activities/SignUpActivity.java new file mode 100644 index 0000000..76bdaa0 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/activities/SignUpActivity.java @@ -0,0 +1,95 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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 app.android.box.waveprotocol.org.androidwave.activities; + +import android.app.Activity; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import app.android.box.waveprotocol.org.androidwave.R; +import app.android.box.waveprotocol.org.androidwave.service.WaveService; +import app.android.box.waveprotocol.org.androidwave.util.Util; + +public class SignUpActivity extends Activity { + + EditText username; + EditText password; + EditText reEnterPassword; + Button signUp; + TextView signIn; + + AsyncTask<String, Void, Boolean> waveSignUpTask; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.signup_activity); + + username = (EditText) findViewById(R.id.input_username); + password = (EditText) findViewById(R.id.input_password); + reEnterPassword = (EditText) findViewById(R.id.input_reEnterPassword); + signUp = (Button) findViewById(R.id.btn_signIn); + signIn = (TextView) findViewById(R.id.link_signup); + + waveSignUpTask = new AsyncTask<String, Void, Boolean>() { + + @Override + protected Boolean doInBackground(String... params) { + WaveService waveService = new WaveService(); + return waveService.waveSignUpTask(params[0], params[1], params[2]); + } + + + @Override + protected void onPostExecute(Boolean signUpResult) { + if (signUpResult) { + Intent openLoginActivity = new Intent("app.android.box.waveprotocol.org.androidwave.LOGINACTIVITY"); + startActivity(openLoginActivity); + Toast.makeText(SignUpActivity.this, "User sign up successfully", Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(SignUpActivity.this, "User sign up fail", Toast.LENGTH_LONG).show(); + Toast.makeText(SignUpActivity.this, "Please try again later...", Toast.LENGTH_LONG).show(); + } + } + }; + + signUp.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + waveSignUpTask.execute(Util.getHostAndUserNames(username.getText().toString())[1], Util.getHostAndUserNames(username.getText().toString())[0], password.getText().toString()); + } + }); + + signIn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent openLoginActivity = new Intent("app.android.box.waveprotocol.org.androidwave.LOGINACTIVITY"); + startActivity(openLoginActivity); + } + }); + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/activities/TestMainActivity.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/activities/TestMainActivity.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/activities/TestMainActivity.java new file mode 100644 index 0000000..d809336 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/activities/TestMainActivity.java @@ -0,0 +1,124 @@ +package app.android.box.waveprotocol.org.androidwave.activities; + +import android.app.ListActivity; +import android.content.Intent; +import android.graphics.drawable.AnimationDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import app.android.box.waveprotocol.org.androidwave.R; + +/** + * Created by roellk on 8/20/2015. + */ +public class TestMainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener{ + public static final String TYPE = "TYPE"; + private DataSource mDataSource; + private ListView mListView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main_activity); + + mListView = (ListView) findViewById(R.id.listView); + mDataSource = new DataSource(this); + mListView.setAdapter(new SampleAdapter()); + mListView.setOnItemClickListener(this); + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + DataItem item = (DataItem) mListView.getItemAtPosition(position); + + // if navigation is supported, open the next activity + if (item.getNavigationInfo() != DataSource.NO_NAVIGATION) { + Intent intent = new Intent(this, ListActivity.class); + intent.putExtra(TYPE, item.getNavigationInfo()); + startActivity(intent); + } + } + + private class SampleAdapter extends BaseAdapter { + + @Override + public int getCount() { + return mDataSource.getCount(); + } + + @Override + public DataItem getItem(int position) { + return mDataSource.getItem(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder holder; + if (convertView == null) { + convertView = View.inflate(TestMainActivity.this, R.layout.list_item_layout, null); + holder = new ViewHolder(convertView); + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); + } + + DataItem item = getItem(position); + + final Drawable drawable = item.getDrawable(); + holder.imageView.setImageDrawable(drawable); + holder.textView.setText(item.getLabel()); + + // if navigation is supported, show the ">" navigation icon + if (item.getNavigationInfo() != DataSource.NO_NAVIGATION) { + holder.textView.setCompoundDrawablesWithIntrinsicBounds(null, + null, + getResources().getDrawable(R.drawable.ic_action_next_item), + null); + } + else { + holder.textView.setCompoundDrawablesWithIntrinsicBounds(null, + null, + null, + null); + } + + // fix for animation not playing for some below 4.4 devices + if (drawable instanceof AnimationDrawable) { + holder.imageView.post(new Runnable() { + @Override + public void run() { + ((AnimationDrawable) drawable).stop(); + ((AnimationDrawable) drawable).start(); + } + }); + } + + return convertView; + } + } + + private static class ViewHolder { + + private ImageView imageView; + + private TextView textView; + + private ViewHolder(View view) { + imageView = (ImageView) view.findViewById(R.id.imageView); + textView = (TextView) view.findViewById(R.id.textView); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/ClientPercentEncoderDecoder.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/ClientPercentEncoderDecoder.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/ClientPercentEncoderDecoder.java new file mode 100644 index 0000000..a15b592 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/ClientPercentEncoderDecoder.java @@ -0,0 +1,30 @@ +package app.android.box.waveprotocol.org.androidwave.service; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; + +import org.waveprotocol.wave.model.id.URIEncoderDecoder; +import org.waveprotocol.wave.model.id.URIEncoderDecoder.EncodingException; + +public class ClientPercentEncoderDecoder implements URIEncoderDecoder.PercentEncoderDecoder { + + @Override + public String encode(String encodingValue) throws EncodingException { + String encodedValue; + try { + return URLEncoder.encode(encodingValue, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new EncodingException("Unable to encoding value " + encodingValue ); + } + } + + @Override + public String decode(String decodingValue) throws EncodingException { + try { + return URLDecoder.decode(decodingValue, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new EncodingException("Unable to decoding value " + decodingValue ); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/RemoteViewServiceMultiplexer.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/RemoteViewServiceMultiplexer.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/RemoteViewServiceMultiplexer.java new file mode 100644 index 0000000..1ffb832 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/RemoteViewServiceMultiplexer.java @@ -0,0 +1,22 @@ +package app.android.box.waveprotocol.org.androidwave.service; + +import org.waveprotocol.box.common.comms.ProtocolWaveletUpdate; + +public class RemoteViewServiceMultiplexer implements WaveWebSocketCallback { + + private final WaveWebSocketClient socket; + + private final String userId; + + public RemoteViewServiceMultiplexer(WaveWebSocketClient socket, String userId) { + this.socket = socket; + this.userId = userId; + + socket.attachHandler(this); + } + + @Override + public void onWaveletUpdate(ProtocolWaveletUpdate message) { + + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/RemoteWaveViewService.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/RemoteWaveViewService.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/RemoteWaveViewService.java new file mode 100644 index 0000000..98e8cad --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/RemoteWaveViewService.java @@ -0,0 +1,42 @@ +package app.android.box.waveprotocol.org.androidwave.service; + +import org.waveprotocol.wave.concurrencycontrol.channel.WaveViewService; +import org.waveprotocol.wave.concurrencycontrol.wave.CcDataDocumentImpl; +import org.waveprotocol.wave.model.id.IdFilter; +import org.waveprotocol.wave.model.id.WaveId; +import org.waveprotocol.wave.model.id.WaveletId; +import org.waveprotocol.wave.model.id.WaveletName; +import org.waveprotocol.wave.model.operation.wave.WaveletDelta; +import org.waveprotocol.wave.model.version.HashedVersion; + +import java.util.List; +import java.util.Map; + +import app.android.box.waveprotocol.org.androidwave.service.documents.WaveDocuments; + + +public class RemoteWaveViewService implements WaveViewService { + + public RemoteWaveViewService(WaveId waveId, RemoteViewServiceMultiplexer channel, WaveDocuments<CcDataDocumentImpl> documentRegistry) { + } + + @Override + public void viewOpen(IdFilter idFilter, Map<WaveletId, List<HashedVersion>> map, OpenCallback openCallback) { + + } + + @Override + public String viewSubmit(WaveletName waveletName, WaveletDelta waveletDelta, String s, SubmitCallback submitCallback) { + return null; + } + + @Override + public void viewClose(WaveId waveId, String s, CloseCallback closeCallback) { + + } + + @Override + public String debugGetProfilingInfo(String s) { + return null; + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/SubmitResponseCallback.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/SubmitResponseCallback.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/SubmitResponseCallback.java new file mode 100644 index 0000000..4f60e45 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/SubmitResponseCallback.java @@ -0,0 +1,8 @@ +package app.android.box.waveprotocol.org.androidwave.service; + +import org.waveprotocol.box.common.comms.ProtocolSubmitResponse; + +public interface SubmitResponseCallback { + + void run(ProtocolSubmitResponse response); +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveRender.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveRender.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveRender.java new file mode 100644 index 0000000..39ce08a --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveRender.java @@ -0,0 +1,285 @@ +package app.android.box.waveprotocol.org.androidwave.service; + +import com.google.common.base.Preconditions; + +import org.waveprotocol.wave.concurrencycontrol.channel.OperationChannelMultiplexer; +import org.waveprotocol.wave.concurrencycontrol.channel.OperationChannelMultiplexerImpl; +import org.waveprotocol.wave.concurrencycontrol.channel.ViewChannelFactory; +import org.waveprotocol.wave.concurrencycontrol.channel.ViewChannelImpl; +import org.waveprotocol.wave.concurrencycontrol.channel.WaveViewService; +import org.waveprotocol.wave.concurrencycontrol.common.UnsavedDataListener; +import org.waveprotocol.wave.concurrencycontrol.common.UnsavedDataListenerFactory; +import org.waveprotocol.wave.concurrencycontrol.wave.CcDataDocumentImpl; +import org.waveprotocol.wave.model.conversation.ObservableConversationView; +import org.waveprotocol.wave.model.conversation.WaveBasedConversationView; +import org.waveprotocol.wave.model.document.WaveContext; +import org.waveprotocol.wave.model.document.indexed.IndexedDocumentImpl; +import org.waveprotocol.wave.model.document.operation.DocInitialization; +import org.waveprotocol.wave.model.document.operation.automaton.DocumentSchema; +import org.waveprotocol.wave.model.id.IdConstants; +import org.waveprotocol.wave.model.id.IdFilter; +import org.waveprotocol.wave.model.id.IdGenerator; +import org.waveprotocol.wave.model.id.IdURIEncoderDecoder; +import org.waveprotocol.wave.model.id.WaveId; +import org.waveprotocol.wave.model.id.WaveletId; +import org.waveprotocol.wave.model.schema.SchemaProvider; +import org.waveprotocol.wave.model.schema.conversation.ConversationSchemas; +import org.waveprotocol.wave.model.util.FuzzingBackOffScheduler; +import org.waveprotocol.wave.model.util.FuzzingBackOffScheduler.CollectiveScheduler; +import org.waveprotocol.wave.model.version.HashedVersion; +import org.waveprotocol.wave.model.version.HashedVersionFactory; +import org.waveprotocol.wave.model.version.HashedVersionZeroFactoryImpl; +import org.waveprotocol.wave.model.wave.ParticipantId; +import org.waveprotocol.wave.model.wave.data.ObservableWaveletData; +import org.waveprotocol.wave.model.wave.data.WaveViewData; +import org.waveprotocol.wave.model.wave.data.impl.ObservablePluggableMutableDocument; +import org.waveprotocol.wave.model.wave.data.impl.WaveViewDataImpl; +import org.waveprotocol.wave.model.wave.data.impl.WaveletDataImpl; +import org.waveprotocol.wave.model.wave.data.DocumentFactory; +import org.waveprotocol.wave.model.wave.opbased.OpBasedWavelet; +import org.waveprotocol.wave.model.wave.opbased.WaveViewImpl; +import org.waveprotocol.wave.model.wave.opbased.WaveViewImpl.WaveletFactory; +import org.waveprotocol.wave.model.wave.opbased.WaveViewImpl.WaveletConfigurator; +import org.waveprotocol.wave.model.waveref.WaveRef; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Set; +import java.util.Timer; + +import app.android.box.waveprotocol.org.androidwave.service.concurrencycontrol.Connector; +import app.android.box.waveprotocol.org.androidwave.service.concurrencycontrol.Connector.Command; +import app.android.box.waveprotocol.org.androidwave.service.concurrencycontrol.LiveChannelBinder; +import app.android.box.waveprotocol.org.androidwave.service.concurrencycontrol.WaveletOperationalizer; +import app.android.box.waveprotocol.org.androidwave.service.documents.WaveDocuments; +import app.android.box.waveprotocol.org.androidwave.service.logger.WaveLogger; +import app.android.box.waveprotocol.org.androidwave.service.models.Model; +import app.android.box.waveprotocol.org.androidwave.service.scheduler.OptimalGroupingScheduler; +import app.android.box.waveprotocol.org.androidwave.service.scheduler.Scheduler; +import app.android.box.waveprotocol.org.androidwave.service.scheduler.SchedulerInstance; + +public class WaveRender { + + private final WaveRef waveRef; + private final boolean isNewWave; + private final Set<ParticipantId> otherParticipants; + private ParticipantId signedInuser; + private IdGenerator idGenerator; + + private final RemoteViewServiceMultiplexer channel; + private final UnsavedDataListener unsavedDataListener; + + private WaveViewData waveData; + private Connector connector; + private WaveContext waveContext; + + private ObservableConversationView conversations; + private WaveViewImpl<OpBasedWavelet> wave; + private WaveletOperationalizer wavelets; + private WaveDocuments<CcDataDocumentImpl> documentRegistry; + + private CollectiveScheduler rpcScheduler; + + private boolean isClosed = true; + private Timer timer; + + + public WaveRender(boolean isNewWave, WaveRef waveRef, RemoteViewServiceMultiplexer waveChannel, + ParticipantId waveParticipant, + Set<ParticipantId> waveOtherParticipants, IdGenerator waveIdGenerator, + UnsavedDataListener unsavedDataListener, Timer timer) { + this.signedInuser = waveParticipant; + this.waveRef = waveRef; + this.isNewWave = isNewWave; + this.idGenerator = waveIdGenerator; + this.channel = waveChannel; + this.otherParticipants = waveOtherParticipants; + this.unsavedDataListener = unsavedDataListener; + this.timer = timer; + } + + public void init(Command command) { + + waveData = WaveViewDataImpl.create(waveRef.getWaveId()); + + if (isNewWave) { + + getConversations().createRoot().addParticipantIds(otherParticipants); + getConnector().connect(command); + } else { + getConnector().connect(command); + } + + isClosed = false; + } + + private ObservableConversationView getConversations() { + return conversations == null ? conversations = createConversations() : conversations; + } + + private Connector getConnector() { + return connector == null ? connector = createConnector() : connector; + } + + private ObservableConversationView createConversations() { + return WaveBasedConversationView.create(getWave(), getIdGenerator()); + } + + private WaveViewImpl<OpBasedWavelet> getWave() { + return wave == null ? wave = createWave() : wave; + } + + private IdGenerator getIdGenerator() { + return idGenerator; + } + + public WaveContext getWaveContext() { + + if (isClosed) { + return null; + } + if (waveContext == null) { + waveContext = new WaveContext(getWave(), getConversations(), null, null); + } + + return waveContext; + } + + private WaveViewImpl<OpBasedWavelet> createWave() { + + WaveViewData snapshot = getWaveData(); + + final WaveletOperationalizer operationalizer = getWavelets(); + WaveletFactory<OpBasedWavelet> waveletFactory = new WaveletFactory<OpBasedWavelet>() { + @Override + public OpBasedWavelet create(WaveId waveId, WaveletId id, ParticipantId creator) { + long now = System.currentTimeMillis(); + ObservableWaveletData data = new WaveletDataImpl(id, creator, now, 0L, + HashedVersion.unsigned(0), now, waveId, getDocumentRegistry()); + return operationalizer.operationalize(data); + } + }; + WaveViewImpl<OpBasedWavelet> wave = WaveViewImpl.create(waveletFactory, snapshot.getWaveId(), + getIdGenerator(), getSignedInUser(), WaveletConfigurator.ADD_CREATOR); + + for (ObservableWaveletData waveletData : snapshot.getWavelets()) { + wave.addWavelet(operationalizer.operationalize(waveletData)); + } + return wave; + } + + private WaveViewData getWaveData() { + Preconditions.checkState(waveData != null, "wave not ready"); + return waveData; + } + + private WaveletOperationalizer getWavelets() { + return wavelets == null ? wavelets = createWavelets() : wavelets; + } + + private WaveletOperationalizer createWavelets() { + return WaveletOperationalizer.create(getWaveData().getWaveId(), getSignedInUser()); + } + + private ParticipantId getSignedInUser() { + return signedInuser; + } + + private WaveDocuments<CcDataDocumentImpl> getDocumentRegistry() { + return documentRegistry == null + ? documentRegistry = createDocumentRegistry() : documentRegistry; + } + + private WaveDocuments<CcDataDocumentImpl> createDocumentRegistry() { + IndexedDocumentImpl.performValidation = false; + + DocumentFactory<?> dataDocFactory = + ObservablePluggableMutableDocument.createFactory(createSchemas()); + + DocumentFactory<CcDataDocumentImpl> fakeBlipDocFactory = new DocumentFactory<CcDataDocumentImpl>() { + + @Override + public CcDataDocumentImpl create(WaveletId waveletId, String docId, DocInitialization content) { + return new CcDataDocumentImpl(DocumentSchema.NO_SCHEMA_CONSTRAINTS, content); + } + + }; + + return WaveDocuments.create(fakeBlipDocFactory, dataDocFactory); + } + + private SchemaProvider createSchemas() { + return new ConversationSchemas(); + } + + private Connector createConnector() { + + WaveLogger loggerView = new WaveLogger(); + + IdURIEncoderDecoder uriCodec = new IdURIEncoderDecoder(new ClientPercentEncoderDecoder()); + HashedVersionFactory hashFactory = new HashedVersionZeroFactoryImpl(uriCodec); + + Scheduler scheduler = (Scheduler) new FuzzingBackOffScheduler.Builder(getRpcScheduler()) + .setInitialBackOffMs(1000).setMaxBackOffMs(60000).setRandomisationFactor(0.5).build(); + + ViewChannelFactory viewFactory = ViewChannelImpl.factory(createWaveViewService(), loggerView); + + UnsavedDataListenerFactory unsyncedListeners = new UnsavedDataListenerFactory() { + + private final UnsavedDataListener listener = unsavedDataListener; + + @Override + public UnsavedDataListener create(WaveletId waveletId) { + return listener; + } + + @Override + public void destroy(WaveletId waveletId) { + + } + }; + + WaveletId udwId = getIdGenerator().newUserDataWaveletId(getSignedInUser().getAddress()); + + ArrayList<String> prefixes = new ArrayList<String>(); + prefixes.add(IdConstants.CONVERSATION_WAVELET_PREFIX); + prefixes.add(Model.WAVELET_ID_PREFIX); + + final IdFilter filter = IdFilter.of(Collections.singleton(udwId), prefixes); + + OperationChannelMultiplexerImpl.LoggerContext loggers = new OperationChannelMultiplexerImpl.LoggerContext(loggerView, loggerView,loggerView, loggerView); + + WaveletDataImpl.Factory snapshotFactory = WaveletDataImpl.Factory.create(getDocumentRegistry()); + final OperationChannelMultiplexer mux = new OperationChannelMultiplexerImpl(getWave() + .getWaveId(), viewFactory, snapshotFactory, loggers, unsyncedListeners, (org.waveprotocol.wave.model.util.Scheduler) scheduler, + hashFactory); + + final WaveViewImpl<OpBasedWavelet> wave = getWave(); + + + return new Connector() { + @Override + public void connect(Command onOpened) { + LiveChannelBinder.openAndBind(getWavelets(), wave, getDocumentRegistry(), mux, filter, + onOpened); + } + + @Override + public void close() { + mux.close(); + } + }; + } + + private CollectiveScheduler getRpcScheduler() { + return rpcScheduler == null ? rpcScheduler = createRpcScheduler() : rpcScheduler; + } + + protected WaveViewService createWaveViewService() { + return new RemoteWaveViewService(waveRef.getWaveId(), channel, getDocumentRegistry()); + } + + protected CollectiveScheduler createRpcScheduler() { + return new OptimalGroupingScheduler(SchedulerInstance.getLowPriorityTimer()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveService.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveService.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveService.java new file mode 100644 index 0000000..482e2b2 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveService.java @@ -0,0 +1,191 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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 app.android.box.waveprotocol.org.androidwave.service; + +import android.os.AsyncTask; + +import org.waveprotocol.wave.model.document.WaveContext; +import org.waveprotocol.wave.model.id.IdGenerator; +import org.waveprotocol.wave.model.id.IdGeneratorImpl; +import org.waveprotocol.wave.model.id.WaveId; +import org.waveprotocol.wave.model.wave.ParticipantId; +import org.waveprotocol.wave.model.waveref.WaveRef; +import org.waveprotocol.wave.model.util.Pair; + +import java.util.Collections; +import java.util.Timer; +import java.util.Map; + +import app.android.box.waveprotocol.org.androidwave.service.concurrencycontrol.Connector.Command; +import app.android.box.waveprotocol.org.androidwave.service.models.Model; +import app.android.box.waveprotocol.org.androidwave.service.models.TypeIdGenerator; + +public class WaveService { + + private String waveHost; + private String waveSessionId; + private String waveUsername; + + private Timer timer; + + private ParticipantId waveParticipantId; + private IdGenerator waveIdGenerator; + private TypeIdGenerator waveTypeIdGenerator; + + private WaveWebSocketClient waveWebSocketClient; + private RemoteViewServiceMultiplexer waveChannel; + + private Map<WaveRef, Pair<WaveRender, Model>> waveStore; + + + + public boolean waveSignUpTask(String host, String username, String password){ + WaveSignUp waveSignUpService = new WaveSignUp(); + return waveSignUpService.waveSignUp(host,username,password); + } + + public void waveLoginTask(String host, String username, String password){ + new WaveSession().execute(host,username,password); + } + + public String getWaveSessionId() { + return waveSessionId; + } + + public void setWaveSessionId(String waveSessionId) { + this.waveSessionId = waveSessionId; + } + + public String getWaveHost() { + return waveHost; + } + + public void setWaveHost(String waveHost) { + this.waveHost = waveHost; + } + + public String getWaveUsername() { + return waveUsername; + } + + public void setWaveUsername(String waveUsername) { + this.waveUsername = waveUsername; + } + + public boolean isWaveSessionStarted() { + return waveSessionId != null; + } + + private void openWebSocketConnection(String hostName, String SessionId){ + + String waveWebSocketUrl = "http://"+ hostName +"/atmosphere"; + + waveIdGenerator = new IdGeneratorImpl(waveWebSocketUrl, new IdGeneratorImpl.Seed() { + @Override + public String get() { + return waveSessionId.substring(0, 5); + } + }); + + waveTypeIdGenerator = TypeIdGenerator.get(waveIdGenerator); + + waveWebSocketClient = new WaveWebSocketClient(waveWebSocketUrl, waveSessionId); + + waveWebSocketClient.connect(new WaveWebSocketClient.ConnectionListener() { + @Override + public void onConnect() { + + } + + @Override + public void onReconnect() { + + } + + @Override + public void onDisconnect() { + + } + }); + + waveChannel = new RemoteViewServiceMultiplexer(waveWebSocketClient, waveParticipantId.getAddress()); + + } + + private void closeWebSocket() { + + waveIdGenerator = null; + waveChannel = null; + waveWebSocketClient = null; + } + + public String createModel() { + + WaveId newWaveId = waveTypeIdGenerator.newWaveId(); + final WaveRef waveRef = WaveRef.of(newWaveId); + + final WaveRender waveRender = new WaveRender(true, waveRef, waveChannel, waveParticipantId, + Collections.<ParticipantId>emptySet(), waveIdGenerator, null, timer); + + waveRender.init(new Command() { + + @Override + public void execute() { + + WaveContext wave = waveRender.getWaveContext(); + Model model = Model.create(wave, waveHost, waveParticipantId, true, waveIdGenerator); + + waveStore.put(waveRef, new Pair<WaveRender, Model>(waveRender, model)); + } + }); + + return waveRef.getWaveId().serialise(); + } + + public void openModel(String modelId) { + + } + + public Model getModel(String modelId) { + + return null; + } + + public void closeModel(String modelId) { + + } + + public class WaveSession extends AsyncTask<String, Void, String> { + + @Override + protected String doInBackground(String... params) { + WaveSignIn waveSignIn = new WaveSignIn(); + waveSessionId = waveSignIn.waveSignIn(params[0], params[1], params[2]); + return waveSessionId; + } + + @Override + protected void onPostExecute(String result) { + if (result != null) { + openWebSocketConnection(waveHost, waveSessionId); + } + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveSignIn.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveSignIn.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveSignIn.java new file mode 100644 index 0000000..59aa211 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveSignIn.java @@ -0,0 +1,112 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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 app.android.box.waveprotocol.org.androidwave.service; + +import android.util.Log; + +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.util.List; + +import app.android.box.waveprotocol.org.androidwave.util.Util; + +public class WaveSignIn { + + private static final String CHARSET = "utf-8"; + private static String WAVE_SESSION_COOKIE = "WSESSIONID"; + + /** + * This method get Wave server name, Wave user's username and Wave user's password as input parameters + * and it will invoke AuthenticationServlet in the Wave server. If sign in get success the method + * will return session id + * + * @param host Apache Wave hostname + * @param username Apache Wave user's username + * @param password Apache Wave user's password + * @return Apache Wave user's sessionId + */ + public String waveSignIn(String host, String username, String password) { + + String sessionId = null; + + String servlet = "auth/signin?r=none"; + String hostURL = Util.hostCreator(host, servlet); + String httpQuery = ""; + HttpURLConnection connection = null; + + try { + httpQuery = "address=" + URLEncoder.encode(username, "UTF-8") + "&password=" + + URLEncoder.encode(password, CHARSET) + "&signIn=" + + URLEncoder.encode("Sign+in", CHARSET); + + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + try { + + URL url = new URL(hostURL); + connection = (HttpURLConnection) url.openConnection(); + + connection.setDoOutput(true); + connection.setRequestProperty("Accept-Charset", CHARSET); + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + + CHARSET); + + OutputStream out = connection.getOutputStream(); + out.write(httpQuery.getBytes(CHARSET)); + + + if (connection.getResponseCode() == 200) { + + List<String> cookies = connection.getHeaderFields().get("Set-Cookie"); + + for (String c : cookies) { + if (c.startsWith(WAVE_SESSION_COOKIE)) { + + String cookie = c; + + if (cookie.contains(";")) + cookie = cookie.split(";")[0]; + + sessionId = cookie.split("=")[1]; + break; + } + } + + } + + } catch (Exception e) { + e.printStackTrace(); + return sessionId; + } finally { + + if(connection != null){ + connection.disconnect(); + } + } + + return sessionId; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveSignUp.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveSignUp.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveSignUp.java new file mode 100644 index 0000000..d987e41 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveSignUp.java @@ -0,0 +1,66 @@ +package app.android.box.waveprotocol.org.androidwave.service; + +import android.content.Intent; +import android.os.AsyncTask; +import android.widget.Toast; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; + +import app.android.box.waveprotocol.org.androidwave.util.Util; + +public class WaveSignUp { + + private static final String CHARSET = "utf-8"; + + /** + * This method get Wave server name, Wave user's username and Wave user's password as input parameters + * and it will invoke UserRegistrationServlet in the Wave server. If sign up get success the method + * will return true if not it return false + * + * @param host Apache Wave hostname + * @param username Apache Wave user's username + * @param password Apache Wave user's password + * @return True or false + */ + + public boolean waveSignUp(String host, String username, String password) { + + String servlet = "auth/register"; + String hostURL = Util.hostCreator(host, servlet); + String httpQuery = ""; + HttpURLConnection connection = null; + + try { + httpQuery = "address=" + URLEncoder.encode(username, CHARSET) + "&password=" + + URLEncoder.encode(password, CHARSET); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + try { + URL url = new URL(hostURL); + connection = (HttpURLConnection) url.openConnection(); + connection.setDoOutput(true); + connection.setRequestProperty("Accept-Charset", CHARSET); + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + + CHARSET); + + OutputStream out = connection.getOutputStream(); + out.write(httpQuery.getBytes(CHARSET)); + + return connection.getResponseCode() == 200; + + } catch (IOException e) { + e.printStackTrace(); + return false; + } finally { + connection.disconnect(); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveWebSocket.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveWebSocket.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveWebSocket.java new file mode 100644 index 0000000..77c6be8 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveWebSocket.java @@ -0,0 +1,14 @@ +package app.android.box.waveprotocol.org.androidwave.service; + +public interface WaveWebSocket { + + interface WaveSocketCallback { + void onConnect(); + void onDisconnect(); + void onMessage(String message); + } + + void connect(); + void disconnect(); + void sendMessage(String message); +} http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveWebSocketCallback.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveWebSocketCallback.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveWebSocketCallback.java new file mode 100644 index 0000000..65353f0 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveWebSocketCallback.java @@ -0,0 +1,7 @@ +package app.android.box.waveprotocol.org.androidwave.service; + +import org.waveprotocol.box.common.comms.ProtocolWaveletUpdate; + +public interface WaveWebSocketCallback { + void onWaveletUpdate(ProtocolWaveletUpdate message); +} http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveWebSocketClient.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveWebSocketClient.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveWebSocketClient.java new file mode 100644 index 0000000..a992ba7 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/WaveWebSocketClient.java @@ -0,0 +1,232 @@ +package app.android.box.waveprotocol.org.androidwave.service; + +import android.util.Log; + +import com.google.common.base.Preconditions; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; + +import org.waveprotocol.box.common.comms.gson.ProtocolAuthenticateGsonImpl; +import org.waveprotocol.box.common.comms.gson.ProtocolOpenRequestGsonImpl; +import org.waveprotocol.box.common.comms.gson.ProtocolSubmitRequestGsonImpl; +import org.waveprotocol.box.common.comms.gson.ProtocolSubmitResponseGsonImpl; +import org.waveprotocol.box.common.comms.gson.ProtocolWaveletUpdateGsonImpl; +import org.waveprotocol.wave.communication.gson.GsonException; +import org.waveprotocol.wave.model.util.CollectionUtils; +import org.waveprotocol.wave.model.util.IntMap; + +import java.util.Queue; + +public class WaveWebSocketClient implements WaveWebSocket.WaveSocketCallback { + + private static final String TAG = "WaveWebSocketClient"; + + public interface ConnectionListener { + + public void onConnect(); + + public void onReconnect(); + + public void onDisconnect(); + + } + + private static final int MAX_INITIAL_FAILURES = 2; + + private static final int RECONNECT_TIME_MS = 5000; + + private static final String JETTY_SESSION_TOKEN_NAME = "WSESSIONID"; + + private static class MessageWrapper { + private final static JsonParser parser = new JsonParser(); + + final int sequenceNumber; + final String messageType; + final JsonElement message; + + public MessageWrapper(int sequenceNumber, String messageType, JsonElement message) { + this.sequenceNumber = sequenceNumber; + this.messageType = messageType; + this.message = message; + } + + public static MessageWrapper deserialize(Gson gson, String data) { + JsonElement e = parser.parse(data); + JsonObject obj = e.getAsJsonObject(); + String type = obj.get("messageType").getAsString(); + int seqNo = obj.get("sequenceNumber").getAsInt(); + JsonElement message = obj.get("message"); + return new MessageWrapper(seqNo, type, message); + } + + public static String serialize(String type, int seqno, JsonElement message) { + JsonObject o = new JsonObject(); + o.add("messageType", new JsonPrimitive(type)); + o.add("sequenceNumber", new JsonPrimitive(seqno)); + o.add("message", message); + return o.toString(); + } + } + + private WaveWebSocket socket = null; + private final IntMap<SubmitResponseCallback> submitRequestCallbacks; + + private enum ConnectState { + CONNECTED, CONNECTING, DISCONNECTED + } + + private ConnectState connected = ConnectState.DISCONNECTED; + private WaveWebSocketCallback callback; + private int sequenceNo; + + private final Queue<String> messages = CollectionUtils.createQueue(); + + + private boolean connectedAtLeastOnce = false; + private long connectTry = 0; + private final String urlBase; + private final String httpSessionId; + + private final Gson gson = new Gson(); + + private ConnectionListener connectionListener = null; + + public WaveWebSocketClient(String urlBase, String httpSessionId) { + this.httpSessionId = httpSessionId; + this.urlBase = urlBase; + + submitRequestCallbacks = CollectionUtils.createIntMap(); + //socket = WaveSocketFactory.create(false, urlBase, httpSessionId, this); + } + + public void attachHandler(WaveWebSocketCallback callback) { + Preconditions.checkState(this.callback == null); + Preconditions.checkArgument(callback != null); + this.callback = callback; + } + + public void connect(ConnectionListener listener) { + + connectionListener = listener; + + if (socket == null) { + // socket = WaveSocketFactory.create(true, urlBase, httpSessionId, WaveWebSocketClient.this); + } + + connectTry++; + if (connected == ConnectState.DISCONNECTED) { + Log.i(TAG, "Attemping to reconnect"); + connected = ConnectState.CONNECTING; + socket.connect(); + } + + } + + @Override + public void onConnect() { + + connected = ConnectState.CONNECTED; + + if (httpSessionId != null && !connectedAtLeastOnce) { + ProtocolAuthenticateGsonImpl auth = new ProtocolAuthenticateGsonImpl(); + auth.setToken(httpSessionId); + sendMessage(sequenceNo++, "ProtocolAuthenticate", auth.toGson(null, null)); + } + + while (!messages.isEmpty() && connected == ConnectState.CONNECTED) { + send(messages.poll()); + } + + if (connectionListener != null) + if (!connectedAtLeastOnce) + connectionListener.onConnect(); + else + connectionListener.onReconnect(); + + connectedAtLeastOnce = true; + + } + + @Override + public void onDisconnect() { + connected = ConnectState.DISCONNECTED; + + if (connectionListener != null) + connectionListener.onDisconnect(); + } + + @Override + public void onMessage(final String message) { + Log.i(TAG, "Received JSON message " + message); + MessageWrapper wrapper; + wrapper = MessageWrapper.deserialize(gson, message); + + String messageType = wrapper.messageType; + if ("ProtocolWaveletUpdate".equals(messageType)) { + if (callback != null) { + ProtocolWaveletUpdateGsonImpl waveletUpdate = new ProtocolWaveletUpdateGsonImpl(); + + try { + waveletUpdate.fromGson(wrapper.message, gson, null); + } catch (GsonException e) { + Log.i(TAG, "Error parsing WaveletUpdate JSON message", e); + return; + } + callback.onWaveletUpdate(waveletUpdate); + } + } else if ("ProtocolSubmitResponse".equals(messageType)) { + int seqno = wrapper.sequenceNumber; + SubmitResponseCallback callback = submitRequestCallbacks.get(seqno); + if (callback != null) { + submitRequestCallbacks.remove(seqno); + ProtocolSubmitResponseGsonImpl submitResponse = new ProtocolSubmitResponseGsonImpl(); + try { + submitResponse.fromGson(wrapper.message, gson, null); + } catch (GsonException e) { + Log.e(TAG, "Error parsing SubmitResponse JSON message", e); + return; + } + callback.run(submitResponse); + } + } + } + + public void submit(ProtocolSubmitRequestGsonImpl message, SubmitResponseCallback callback) { + int submitId = sequenceNo++; + submitRequestCallbacks.put(submitId, callback); + sendMessage(submitId, "ProtocolSubmitRequest", message.toGson(null, null)); + } + + public void open(ProtocolOpenRequestGsonImpl message) { + sendMessage(sequenceNo++, "ProtocolOpenRequest", message.toGson(null, null)); + } + + + private void sendMessage(int sequenceNo, String type, JsonElement message) { + + String json = ""; + try { + json = MessageWrapper.serialize(type, sequenceNo, message); + } catch (Exception e) { + Log.e(TAG, "Error serializing message ", e); + } finally { + + } + switch (connected) { + case CONNECTED: + send(json); + break; + default: + messages.add(json); + } + } + + private void send(String json) { + Log.i(TAG, "Sending JSON data " + json); + socket.sendMessage(json); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/Connector.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/Connector.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/Connector.java new file mode 100644 index 0000000..bcd4a33 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/Connector.java @@ -0,0 +1,14 @@ +package app.android.box.waveprotocol.org.androidwave.service.concurrencycontrol; + +public interface Connector { + + public interface Command { + + public void execute(); + + } + + void connect(Command onOpened); + + void close(); +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/LiveChannelBinder.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/LiveChannelBinder.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/LiveChannelBinder.java new file mode 100644 index 0000000..3aed86e --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/LiveChannelBinder.java @@ -0,0 +1,101 @@ +package app.android.box.waveprotocol.org.androidwave.service.concurrencycontrol; + + +import org.waveprotocol.wave.concurrencycontrol.channel.Accessibility; +import org.waveprotocol.wave.concurrencycontrol.channel.OperationChannel; +import org.waveprotocol.wave.concurrencycontrol.channel.OperationChannelMultiplexer; +import org.waveprotocol.wave.concurrencycontrol.common.CorruptionDetail; +import org.waveprotocol.wave.concurrencycontrol.wave.CcDocument; +import org.waveprotocol.wave.model.id.IdFilter; +import org.waveprotocol.wave.model.id.WaveletId; +import org.waveprotocol.wave.model.util.CollectionUtils; +import org.waveprotocol.wave.model.wave.ObservableWavelet; +import org.waveprotocol.wave.model.wave.WaveViewListener; +import org.waveprotocol.wave.model.wave.data.ObservableWaveletData; +import org.waveprotocol.wave.model.wave.opbased.OpBasedWavelet; +import org.waveprotocol.wave.model.wave.opbased.WaveViewImpl; + +import java.util.Collection; + +import app.android.box.waveprotocol.org.androidwave.service.documents.WaveDocuments; + +public final class LiveChannelBinder + implements WaveViewListener, OperationChannelMultiplexer.Listener { + + private final StaticChannelBinder binder; + private final WaveletOperationalizer operationalizer; + private final WaveViewImpl<OpBasedWavelet> wave; + private final OperationChannelMultiplexer mux; + private final Connector.Command whenOpened; + + private LiveChannelBinder(StaticChannelBinder binder, WaveletOperationalizer operationalizer, + WaveViewImpl<OpBasedWavelet> wave, OperationChannelMultiplexer mux, Connector.Command whenOpened) { + this.binder = binder; + this.operationalizer = operationalizer; + this.wave = wave; + this.mux = mux; + this.whenOpened = whenOpened; + } + + public static void openAndBind(WaveletOperationalizer operationalizer, + WaveViewImpl<OpBasedWavelet> wave, + WaveDocuments<? extends CcDocument> docRegistry, + OperationChannelMultiplexer mux, + IdFilter filter, + Connector.Command whenOpened) { + + StaticChannelBinder staticBinder = new StaticChannelBinder(operationalizer, docRegistry); + + LiveChannelBinder liveBinder = + new LiveChannelBinder(staticBinder, operationalizer, wave, mux, whenOpened); + + final Collection<OperationChannelMultiplexer.KnownWavelet> remoteWavelets = CollectionUtils.createQueue(); + final Collection<ObservableWaveletData> localWavelets = CollectionUtils.createQueue(); + for (ObservableWaveletData wavelet : operationalizer.getWavelets()) { + + if (wavelet.getVersion() > 0) { + remoteWavelets.add( + new OperationChannelMultiplexer.KnownWavelet(wavelet, wavelet.getHashedVersion(), Accessibility.READ_WRITE)); + } else { + localWavelets.add(wavelet); + } + } + + wave.addListener(liveBinder); + + mux.open(liveBinder, filter, remoteWavelets); + for (ObservableWaveletData local : localWavelets) { + mux.createOperationChannel(local.getWaveletId(), local.getCreator()); + } + } + + @Override + public void onOperationChannelCreated(OperationChannel operationChannel, ObservableWaveletData observableWaveletData, Accessibility accessibility) { + + } + + @Override + public void onOperationChannelRemoved(OperationChannel operationChannel, WaveletId waveletId) { + + } + + @Override + public void onOpenFinished() { + + } + + @Override + public void onFailed(CorruptionDetail corruptionDetail) { + + } + + @Override + public void onWaveletAdded(ObservableWavelet observableWavelet) { + + } + + @Override + public void onWaveletRemoved(ObservableWavelet observableWavelet) { + + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/LiveTarget.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/LiveTarget.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/LiveTarget.java new file mode 100644 index 0000000..b312f03 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/LiveTarget.java @@ -0,0 +1,50 @@ +package app.android.box.waveprotocol.org.androidwave.service.concurrencycontrol; + +import org.waveprotocol.wave.model.operation.Operation; +import org.waveprotocol.wave.model.operation.OperationException; +import org.waveprotocol.wave.model.operation.OperationRuntimeException; +import org.waveprotocol.wave.model.operation.SilentOperationSink; + +public final class LiveTarget<T, O extends Operation<? super T>> { + + private final T target; + + private final SilentOperationSink<O> executor; + + private final ProxyOperationSink<O> output; + + private LiveTarget(T target, SilentOperationSink<O> executor, ProxyOperationSink<O> output) { + this.target = target; + this.executor = executor; + this.output = output; + } + + public static <T, O extends Operation<? super T>> LiveTarget<T, O> create(final T data) { + ProxyOperationSink<O> output = ProxyOperationSink.create(); + SilentOperationSink<O> executor = new SilentOperationSink<O>() { + @Override + public void consume(O operation) { + try { + operation.apply(data); + } catch (OperationException e) { + // Fail this object permanently + throw new OperationRuntimeException("Error applying op", e); + } + } + }; + return new LiveTarget<T, O>(data, executor, output); + } + + public T getTarget() { + return target; + } + + public SilentOperationSink<O> getExecutorSink() { + return executor; + } + + public ProxyOperationSink<O> getOutputSink() { + return output; + } +} + http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/ProxyOperationSink.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/ProxyOperationSink.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/ProxyOperationSink.java new file mode 100644 index 0000000..4298013 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/ProxyOperationSink.java @@ -0,0 +1,46 @@ +package app.android.box.waveprotocol.org.androidwave.service.concurrencycontrol; + +import com.google.common.base.Preconditions; + +import org.waveprotocol.wave.model.operation.Operation; +import org.waveprotocol.wave.model.operation.SilentOperationSink; +import org.waveprotocol.wave.model.util.CollectionUtils; + +import java.util.Queue; + +public final class ProxyOperationSink<O extends Operation<?>> implements SilentOperationSink<O> { + + private Queue<O> queue; + private SilentOperationSink<O> target; + + private ProxyOperationSink() { + } + + public static <O extends Operation<?>> ProxyOperationSink<O> create() { + return new ProxyOperationSink<O>(); + } + + public void setTarget(SilentOperationSink<O> target) { + Preconditions.checkState(this.target == null); + this.target = target; + + if (queue != null) { + while (!queue.isEmpty()) { + target.consume(queue.poll()); + } + queue = null; + } + } + + @Override + public void consume(O op) { + if (target != null) { + target.consume(op); + } else { + if (queue == null) { + queue = CollectionUtils.createQueue(); + } + queue.add(op); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/StaticChannelBinder.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/StaticChannelBinder.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/StaticChannelBinder.java new file mode 100644 index 0000000..6b9d6bc --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/StaticChannelBinder.java @@ -0,0 +1,17 @@ +package app.android.box.waveprotocol.org.androidwave.service.concurrencycontrol; + +import org.waveprotocol.wave.concurrencycontrol.wave.CcDocument; + +import app.android.box.waveprotocol.org.androidwave.service.documents.WaveDocuments; + +public final class StaticChannelBinder { + + private final WaveletOperationalizer operationalizer; + private final WaveDocuments<? extends CcDocument> docRegistry; + + public StaticChannelBinder( + WaveletOperationalizer operationalizer, WaveDocuments<? extends CcDocument> docRegistry) { + this.operationalizer = operationalizer; + this.docRegistry = docRegistry; + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/WaveletOperationalizer.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/WaveletOperationalizer.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/WaveletOperationalizer.java new file mode 100644 index 0000000..ed03a10 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/concurrencycontrol/WaveletOperationalizer.java @@ -0,0 +1,71 @@ +package app.android.box.waveprotocol.org.androidwave.service.concurrencycontrol; + +import com.google.common.base.Preconditions; + +import org.waveprotocol.wave.model.id.ModernIdSerialiser; +import org.waveprotocol.wave.model.id.WaveId; +import org.waveprotocol.wave.model.operation.wave.WaveletOperation; +import org.waveprotocol.wave.model.operation.wave.WaveletOperationContext; +import org.waveprotocol.wave.model.operation.wave.BasicWaveletOperationContextFactory; +import org.waveprotocol.wave.model.operation.wave.WaveletOperationContext.Factory; +import org.waveprotocol.wave.model.util.CollectionUtils; +import org.waveprotocol.wave.model.util.ReadableStringMap; +import org.waveprotocol.wave.model.util.StringMap; +import org.waveprotocol.wave.model.wave.ParticipantId; +import org.waveprotocol.wave.model.wave.ParticipationHelper; +import org.waveprotocol.wave.model.wave.data.ObservableWaveletData; +import org.waveprotocol.wave.model.wave.opbased.OpBasedWavelet; + +import java.util.Collection; + +public class WaveletOperationalizer { + + private final WaveId waveId; + private final StringMap<LiveTarget<ObservableWaveletData, WaveletOperation>> wavelets = + CollectionUtils.createStringMap(); + private final WaveletOperationContext.Factory opContextFactory; + + private WaveletOperationalizer(WaveId waveId, Factory opContextFactory) { + this.waveId = waveId; + this.opContextFactory = opContextFactory; + } + + public static WaveletOperationalizer create(WaveId wave, ParticipantId user) { + WaveletOperationContext.Factory opContexts = new BasicWaveletOperationContextFactory(user); + return new WaveletOperationalizer(wave, opContexts); + } + + public OpBasedWavelet operationalize(ObservableWaveletData data) { + LiveTarget<ObservableWaveletData, WaveletOperation> target = createSinks(data); + return new OpBasedWavelet(waveId, + data, + opContextFactory, + ParticipationHelper.DEFAULT, + target.getExecutorSink(), + target.getOutputSink()); + } + + private LiveTarget<ObservableWaveletData, WaveletOperation> createSinks( + ObservableWaveletData data) { + return putAndReturn(wavelets, + ModernIdSerialiser.INSTANCE.serialiseWaveletId(data.getWaveletId()), + LiveTarget.<ObservableWaveletData, WaveletOperation>create(data)); + } + + private static <V> V putAndReturn(StringMap<V> map, String key, V value) { + Preconditions.checkState(!map.containsKey(key)); + map.put(key, value); + return value; + } + + public Collection<ObservableWaveletData> getWavelets() { + final Collection<ObservableWaveletData> targets = CollectionUtils.createQueue(); + this.wavelets.each(new ReadableStringMap.ProcV<LiveTarget<ObservableWaveletData, WaveletOperation>>() { + @Override + public void apply(String id, LiveTarget<ObservableWaveletData, WaveletOperation> triple) { + targets.add(triple.getTarget()); + } + }); + return targets; + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/documents/DocumentRegistry.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/documents/DocumentRegistry.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/documents/DocumentRegistry.java new file mode 100644 index 0000000..08e9893 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/documents/DocumentRegistry.java @@ -0,0 +1,8 @@ +package app.android.box.waveprotocol.org.androidwave.service.documents; + +import org.waveprotocol.wave.model.conversation.ConversationBlip; + +public interface DocumentRegistry<D> { + D get(ConversationBlip blip); +} + http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/documents/WaveDocuments.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/documents/WaveDocuments.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/documents/WaveDocuments.java new file mode 100644 index 0000000..114776b --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/documents/WaveDocuments.java @@ -0,0 +1,65 @@ +package app.android.box.waveprotocol.org.androidwave.service.documents; + +import com.google.common.base.Preconditions; + +import org.waveprotocol.wave.model.conversation.ConversationBlip; +import org.waveprotocol.wave.model.document.operation.DocInitialization; +import org.waveprotocol.wave.model.id.IdUtil; +import org.waveprotocol.wave.model.id.ModernIdSerialiser; +import org.waveprotocol.wave.model.id.WaveletId; +import org.waveprotocol.wave.model.util.CollectionUtils; +import org.waveprotocol.wave.model.util.StringMap; +import org.waveprotocol.wave.model.wave.data.DocumentFactory; +import org.waveprotocol.wave.model.wave.data.DocumentOperationSink; + +public final class WaveDocuments<BlipDocument extends DocumentOperationSink> + implements DocumentFactory<DocumentOperationSink>, DocumentRegistry<BlipDocument> { + + private final DocumentFactory<BlipDocument> blipDocFactory; + private final DocumentFactory<?> dataDocFactory; + private final StringMap<StringMap<BlipDocument>> blips = CollectionUtils.createStringMap(); + + private WaveDocuments(DocumentFactory<BlipDocument> blip, DocumentFactory<?> data) { + this.blipDocFactory = blip; + this.dataDocFactory = data; + } + + public static <B extends DocumentOperationSink> WaveDocuments<B> create( + DocumentFactory<B> blipDocFactory, DocumentFactory<?> dataDocFactory) { + return new WaveDocuments<B>(blipDocFactory, dataDocFactory); + } + + @Override + public DocumentOperationSink create( + final WaveletId waveletId, final String blipId, final DocInitialization content) { + + String waveletIdStr = ModernIdSerialiser.INSTANCE.serialiseWaveletId(waveletId); + if (IdUtil.isBlipId(blipId)) { + BlipDocument document = blipDocFactory.create(waveletId, blipId, content); + StringMap<BlipDocument> convDocuments = getConversationDocuments(waveletIdStr); + Preconditions.checkState(!convDocuments.containsKey(blipId)); + convDocuments.put(blipId, document); + return document; + } else { + return dataDocFactory.create(waveletId, blipId, content); + } + } + + private StringMap<BlipDocument> getConversationDocuments(String id) { + StringMap<BlipDocument> convDocuments = blips.get(id); + if (convDocuments == null) { + convDocuments = CollectionUtils.createStringMap(); + blips.put(id, convDocuments); + } + return convDocuments; + } + + public BlipDocument get(ConversationBlip blip) { + return getBlipDocument(blip.getConversation().getId(), blip.getId()); + } + + public BlipDocument getBlipDocument(String waveletId, String docId) { + StringMap<BlipDocument> convDocuments = blips.get(waveletId); + return convDocuments != null ? convDocuments.get(docId) : null; + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/logger/WaveLogger.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/logger/WaveLogger.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/logger/WaveLogger.java new file mode 100644 index 0000000..5996203 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/logger/WaveLogger.java @@ -0,0 +1,33 @@ +package app.android.box.waveprotocol.org.androidwave.service.logger; + +import org.waveprotocol.wave.common.logging.AbstractLogger; +import org.waveprotocol.wave.common.logging.Logger; +import org.waveprotocol.wave.common.logging.LoggerBundle; + +public class WaveLogger implements LoggerBundle { + + @Override + public void log(AbstractLogger.Level level, Object... objects) { + + } + + @Override + public Logger trace() { + return null; + } + + @Override + public Logger error() { + return null; + } + + @Override + public Logger fatal() { + return null; + } + + @Override + public boolean isModuleEnabled() { + return false; + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/IdGeneratorGeneric.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/IdGeneratorGeneric.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/IdGeneratorGeneric.java new file mode 100644 index 0000000..b6fa2d8 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/IdGeneratorGeneric.java @@ -0,0 +1,12 @@ +package app.android.box.waveprotocol.org.androidwave.service.models; + +import org.waveprotocol.wave.model.id.IdGenerator; +import org.waveprotocol.wave.model.id.WaveId; + +public interface IdGeneratorGeneric { + + IdGeneratorGeneric initialize(IdGenerator idGenerator); + + WaveId newWaveId(); + +} http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/ListElementFactory.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/ListElementFactory.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/ListElementFactory.java new file mode 100644 index 0000000..37db889 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/ListElementFactory.java @@ -0,0 +1,55 @@ +package app.android.box.waveprotocol.org.androidwave.service.models; + +import org.waveprotocol.wave.model.document.Doc.E; +import org.waveprotocol.wave.model.document.util.DocumentEventRouter; +import org.waveprotocol.wave.model.util.Preconditions; + +import java.util.Map; + +public class ListElementFactory implements + org.waveprotocol.wave.model.adt.docbased.Factory<E, Type, ListElementInitializer> { + + + private Model model; + + protected ListElementFactory(Model model) { + this.model = model; + } + + @Override + public Type adapt(DocumentEventRouter<? super E, E, ?> router, E element) { + + Map<String, String> attributes = router.getDocument().getAttributes(element); + Preconditions.checkArgument(attributes != null, + "Adapting a list element to Type but attributes not found"); + + String type = attributes.get("t"); + Preconditions.checkArgument(type != null, + "Adapting a list element to Type but attribute for type not found"); + + String value = attributes.get("r"); + Preconditions.checkArgument(value != null, + "Adapting a list element to Type but attribute for reference not found"); + + return Type.createInstance(type, value, model); + + } + + @Override + public org.waveprotocol.wave.model.adt.docbased.Initializer createInitializer( + final ListElementInitializer initialState) { + + return new org.waveprotocol.wave.model.adt.docbased.Initializer() { + + @Override + public void initialize(Map<String, String> target) { + target.put("t", initialState.getType()); + if (initialState.getBackendId() != null) { + target.put("r", initialState.getBackendId()); + } + } + + }; + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/ListElementInitializer.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/ListElementInitializer.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/ListElementInitializer.java new file mode 100644 index 0000000..50c9885 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/ListElementInitializer.java @@ -0,0 +1,9 @@ +package app.android.box.waveprotocol.org.androidwave.service.models; + +public interface ListElementInitializer { + + String getType(); + + String getBackendId(); + +} http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/ListType.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/ListType.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/ListType.java new file mode 100644 index 0000000..ed79f0b --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/ListType.java @@ -0,0 +1,211 @@ +package app.android.box.waveprotocol.org.androidwave.service.models; + + +import org.waveprotocol.wave.model.adt.ObservableElementList; +import org.waveprotocol.wave.model.adt.docbased.DocumentBasedElementList; +import org.waveprotocol.wave.model.document.Doc; +import org.waveprotocol.wave.model.document.ObservableDocument; +import org.waveprotocol.wave.model.document.util.DefaultDocEventRouter; +import org.waveprotocol.wave.model.document.util.DocEventRouter; +import org.waveprotocol.wave.model.document.util.DocHelper; +import org.waveprotocol.wave.model.util.CopyOnWriteSet; +import org.waveprotocol.wave.model.util.Preconditions; +import org.waveprotocol.wave.model.wave.SourcesEvents; + +import java.util.Collections; + +public class ListType extends Type implements SourcesEvents<ListType.Listener> { + + public final static String PREFIX = "list"; + public final static String ROOT_TAG = "list"; + private final static String ITEM_TAG = "item"; + private final CopyOnWriteSet<Listener> listeners = CopyOnWriteSet.create(); + private ObservableElementList<Type, ListElementInitializer> observableList; + private ObservableElementList.Listener<Type> observableListListener; + private Model model; + private String backendDocumentId; + private ObservableDocument backendDocument; + private Doc.E backendRootElement; + private boolean isAttached; + + + protected ListType(Model model) { + this.model = model; + this.isAttached = false; + + + observableListListener = new ObservableElementList.Listener<Type>() { + + @Override + public void onValueAdded(Type entry) { + for (Listener l : listeners) + l.onValueAdded(entry); + } + + @Override + public void onValueRemoved(Type entry) { + for (Listener l : listeners) + l.onValueRemoved(entry); + } + + }; + } + + protected static Type createAndAttach(Model model, String id) { + + Preconditions.checkArgument(id.startsWith(PREFIX), "ListType.createAndAttach() not a list id"); + ListType list = new ListType(model); + list.attach(id); + return list; + + } + + @Override + protected String getPrefix() { + return PREFIX; + } + + @Override + protected void attach(String docId) { + + if (docId == null) { + + docId = model.generateDocId(getPrefix()); + backendDocument = model.createDocument(docId); + + } else + backendDocument = model.getDocument(docId); + + backendDocumentId = docId; + + backendRootElement = DocHelper.getElementWithTagName(backendDocument, ROOT_TAG); + if (backendRootElement == null) + backendRootElement = + backendDocument.createChildElement(backendDocument.getDocumentElement(), ROOT_TAG, + Collections.<String, String>emptyMap()); + + DocEventRouter router = DefaultDocEventRouter.create(backendDocument); + + this.observableList = + DocumentBasedElementList.create(router, backendRootElement, ITEM_TAG, + new ListElementFactory(model)); + this.observableList.addListener(observableListListener); + + this.isAttached = true; + } + + protected void deAttach() { + Preconditions.checkArgument(isAttached, "Unable to deAttach an unattached MapType"); + } + + @Override + protected boolean isAttached() { + return isAttached; + } + + @Override + protected String serializeToModel() { + Preconditions.checkArgument(isAttached, "Unable to serialize an unattached ListType"); + return backendDocumentId; + } + + @Override + protected ListElementInitializer getListElementInitializer() { + return new ListElementInitializer() { + + @Override + public String getType() { + return PREFIX; + } + + @Override + public String getBackendId() { + return serializeToModel(); + } + + }; + } + + @Override + public void addListener(Listener listener) { + listeners.add(listener); + } + + @Override + public void removeListener(Listener listener) { + listeners.remove(listener); + } + + public Type add(Type value) { + Preconditions.checkArgument(isAttached, "ListType.add(): not attached to model"); + Preconditions.checkArgument(!value.isAttached(), + "ListType.add(): forbidden to add an already attached Type"); + + value.attach(null); + + return observableList.add(value.getListElementInitializer()); + } + + public Type add(int index, Type value) { + + Preconditions.checkArgument(index >= 0 && index <= observableList.size(), + "ListType.add(): add to index out of bounds"); + Preconditions.checkArgument(isAttached, "ListType.add(): not attached to model"); + Preconditions.checkArgument(!value.isAttached(), + "ListType.add(): forbidden to add an already attached Type"); + + value.attach(null); + + return observableList.add(index, value.getListElementInitializer()); + } + + public Type remove(int index) { + if (observableList == null) return null; + Type removedInstance = observableList.get(index); + if (!observableList.remove(removedInstance)) return null; + return removedInstance; + } + + public Type get(int index) { + if (observableList == null) return null; + Preconditions.checkArgument(index >= 0 && index < observableList.size(), + "ListType.get(): add to index out of bounds"); + return observableList.get(index); + } + + public int indexOf(Type type) { + return observableList != null ? observableList.indexOf(type) : -1; + } + + public int size() { + return observableList != null ? observableList.size() : 0; + } + + public Iterable<Type> getValues() { + return observableList != null ? observableList.getValues() : Collections.<Type>emptyList(); + } + + @Override + public String getDocumentId() { + return backendDocumentId; + } + + @Override + public Model getModel() { + return model; + } + + @Override + public String getType() { + return "ListType"; + } + + public interface Listener { + + void onValueAdded(Type entry); + + void onValueRemoved(Type entry); + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-wave-android/blob/514564b1/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/MapSerializer.java ---------------------------------------------------------------------- diff --git a/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/MapSerializer.java b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/MapSerializer.java new file mode 100644 index 0000000..e42b673 --- /dev/null +++ b/app/src/main/java/app/android/box/waveprotocol/org/androidwave/service/models/MapSerializer.java @@ -0,0 +1,47 @@ +package app.android.box.waveprotocol.org.androidwave.service.models; + +public class MapSerializer implements org.waveprotocol.wave.model.util.Serializer<Type> { + + protected Model model; + + protected MapSerializer(Model model) { + this.model = model; + } + + @Override + public String toString(Type x) { + return x.serializeToModel(); + } + + @Override + public Type fromString(String s) { + + if (s.startsWith(StringType.PREFIX)) { + + return StringType.createAndAttach(model, s); + + } else if (s.startsWith(MapType.PREFIX)) { + + return MapType.createAndAttach(model, s); + + } else if (s.startsWith(ListType.PREFIX)) { + + return ListType.createAndAttach(model, s); + + } else if (s.startsWith(TextType.PREFIX)) { + + return TextType.createAndAttach(model, s); + } + + + return null; + } + + @Override + public Type fromString(String s, Type defaultValue) { + if (s == null) return defaultValue; + Type instance = fromString(s); + return instance != null ? instance : defaultValue; + } + +}
