/** * Copyright (c) 2002-2013 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.noser.neo4j.android.dbinspector.implementation; import java.util.ArrayList; import java.util.Comparator; import java.util.Formatter; import java.util.List; import java.util.Set; import java.util.TreeSet; import org.neo4j.android.client.GraphDatabase; import org.neo4j.android.client.Neo4jServiceException; import org.neo4j.android.common.ParcelableNode; import org.neo4j.android.common.ParcelableRelationship; import roboguice.activity.RoboActivity; import roboguice.inject.ContentView; import roboguice.inject.InjectExtra; import roboguice.inject.InjectResource; import roboguice.inject.InjectView; import roboguice.util.Ln; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.os.RemoteException; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.ImageButton; import android.widget.LinearLayout; import com.google.inject.Inject; import com.noser.neo4j.android.dbinspector.R; import com.noser.neo4j.android.dbinspector.base.DBInspectorConstants; import com.noser.neo4j.android.dbinspector.interfaces.IDBManager; @ContentView(R.layout.node_activity) public class NodeActivity extends RoboActivity { @Inject private Context context; @Inject private IDBManager dbManager; @InjectView(R.id.nodeRelationshipsLayout) private LinearLayout nodeRelationshipsLayout; @Inject private LayoutInflater inflater; @InjectExtra(value = DBInspectorConstants.INTENTEXTRA_CENTER_NODEID) private long centerNodeId; @InjectView(R.id.nodeCreateButton) private ImageButton nodeCreateButton; @InjectView(R.id.nodeCenterButton) private Button nodeCenterButton; @InjectView(R.id.nodeLoopRelationshipsButton) private Button nodeLoopRelationshipsButton; @InjectResource(R.string.node_create_default_relationship_name) private String defaultRelationshipName; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle bundle = getIntent().getExtras(); if (bundle.containsKey(DBInspectorConstants.INTENTEXTRA_MOVE_LEFT) && bundle.getBoolean(DBInspectorConstants.INTENTEXTRA_MOVE_LEFT)) { overridePendingTransition(android.R.anim.slide_in_left, android.R.anim.slide_out_right); } setTitle(getResources().getString(R.string.database_title) + " " + dbManager.getCurrentNeo4jDatabaseName()); nodeCreateButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { askCreateNode(); } }); } @Override protected void onResume() { super.onResume(); updateNodesList(); } private void updateNodesList() { try { nodeRelationshipsLayout.removeAllViews(); GraphDatabase database = dbManager.getCurrentNeo4jDatabase(); database.beginTx(); try { ParcelableNode centerNode = database.getNodeById(centerNodeId); List<ParcelableRelationship> relationships = centerNode.getRelationships(); final Set<Long> pointingToCenterNodeNodeIds = new TreeSet<Long>(); Set<ParcelableNode> peerNodes = new TreeSet<ParcelableNode>(new Comparator<ParcelableNode>() { @Override public int compare(ParcelableNode firstNode, ParcelableNode secondNode) { if (firstNode.getId() < secondNode.getId()) { return -1; } else if (firstNode.getId() > secondNode.getId()) { return 1; } else { return 0; } } }); int loopRelationshipsCount = 0; String lastLoopRelationshipName = ""; for (ParcelableRelationship relationship : relationships) { if (relationship.getStartNodeId() == relationship.getEndNodeId()) { loopRelationshipsCount++; lastLoopRelationshipName = relationship.getName(); } else { if (centerNodeId == relationship.getStartNodeId()) { peerNodes.add(database.getNodeById(relationship.getEndNodeId())); } else { pointingToCenterNodeNodeIds.add(relationship.getStartNodeId()); peerNodes.add(database.getNodeById(relationship.getStartNodeId())); } } } nodeCenterButton.setText("" + centerNodeId); nodeCenterButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (pointingToCenterNodeNodeIds.isEmpty()) { // do nothing. } else if (pointingToCenterNodeNodeIds.size() == 1) { showNodeActivity(pointingToCenterNodeNodeIds.iterator().next(), true); } else { showRelatedStartNodes(pointingToCenterNodeNodeIds); } } }); String text = getResources().getString(R.string.node_relationships_loop_button); if (loopRelationshipsCount == 0) { nodeLoopRelationshipsButton.setText(text); } else if (loopRelationshipsCount == 1) { nodeLoopRelationshipsButton.setText(text + "\n" + lastLoopRelationshipName); } else { nodeLoopRelationshipsButton.setText(text + "\n# " + loopRelationshipsCount); } nodeLoopRelationshipsButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showRelationshipActivity(centerNodeId, centerNodeId); } }); for (ParcelableNode peerNode : peerNodes) { addNodeRelationshipsListItem(peerNode); } } finally { database.txFinish(); } } catch (Exception e) { Ln.e(e, "database exception"); showErrorDialog(); } } private void showRelatedStartNodes(Set<Long> nodeIdSet) { final List<Long> nodeIds = new ArrayList<Long>(nodeIdSet); final CharSequence[] items = new CharSequence[nodeIds.size()]; int index = 0; for (Long nodeId : nodeIds) { items[index] = nodeId.toString(); index++; } Formatter formatter = new Formatter(); formatter.format(getResources().getString(R.string.node_select_related_node_question), centerNodeId); AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(formatter.toString()); builder.setItems(items, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { showNodeActivity(nodeIds.get(item), true); } }); AlertDialog dialog = builder.create(); dialog.show(); formatter.close(); } private void addNodeRelationshipsListItem(ParcelableNode peerNode) { final long peerNodeId = peerNode.getId(); List<ParcelableRelationship> relationships = peerNode.getRelationships(); boolean foundBackwardRelationship = false; boolean foundForwardRelationship = false; long lastRelationshipId = 0; String lastRelationshipName = ""; int relationshipCount = 0; for (ParcelableRelationship relationship : relationships) { if (relationship.getStartNodeId() == centerNodeId) { foundForwardRelationship = true; relationshipCount++; lastRelationshipId = relationship.getId(); lastRelationshipName = relationship.getName(); } if (relationship.getEndNodeId() == centerNodeId) { foundBackwardRelationship = true; relationshipCount++; lastRelationshipId = relationship.getId(); lastRelationshipName = relationship.getName(); } } View nodeRelationshipsListItem = inflater.inflate(R.layout.node_activity_listitem, null); Button relationshipsButton = (Button) nodeRelationshipsListItem.findViewById(R.id.nodeItemRelationshipButton); String text; if (foundForwardRelationship && foundBackwardRelationship) { text = getResources().getString(R.string.node_relationships_both_button); } else if (foundForwardRelationship) { text = getResources().getString(R.string.node_relationships_forward_button); } else if (foundBackwardRelationship) { text = getResources().getString(R.string.node_relationships_backward_button); } else { text = getResources().getString(R.string.node_relationships_unknown_button); } if (relationshipCount == 1) { relationshipsButton.setText(text + "\n" + lastRelationshipId + "\n" + lastRelationshipName); } else { relationshipsButton.setText(text + "\n# " + relationshipCount); } relationshipsButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showRelationshipActivity(centerNodeId, peerNodeId); } }); Button nodeButton = (Button) nodeRelationshipsListItem.findViewById(R.id.nodeItemNodeButton); nodeButton.setText("" + peerNode.getId()); nodeButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showNodeActivity(peerNodeId, false); } }); nodeRelationshipsLayout.addView(nodeRelationshipsListItem); } private void showErrorDialog() { AlertDialog dialog = new AlertDialog.Builder(context).create(); dialog.setTitle(R.string.error_title); dialog.setMessage(getResources().getText(R.string.database_error)); dialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); dialog.show(); } private void showNodeActivity(long nodeId, boolean moveLeft) { Intent intent = new Intent(getApplicationContext(), NodeActivity.class); intent.putExtra(DBInspectorConstants.INTENTEXTRA_CENTER_NODEID, nodeId); intent.putExtra(DBInspectorConstants.INTENTEXTRA_MOVE_LEFT, moveLeft); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); startActivity(intent); finish(); if (moveLeft) { overridePendingTransition(android.R.anim.slide_in_left, android.R.anim.slide_out_right); } } private void showRelationshipActivity(long centerNodeId, long peerNodeId) { Intent intent = new Intent(getApplicationContext(), RelationshipActivity.class); intent.putExtra(DBInspectorConstants.INTENTEXTRA_CENTER_NODEID, centerNodeId); intent.putExtra(DBInspectorConstants.INTENTEXTRA_PEER_NODEID, peerNodeId); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); startActivity(intent); } private void askCreateNode() { AlertDialog dialog = new AlertDialog.Builder(this).create(); dialog.setTitle(R.string.node_create_title); View nodeCreateView = inflater.inflate(R.layout.node_create, null); final CheckBox nodeCreateDefaultRelationShipCheckBox = (CheckBox) nodeCreateView .findViewById(R.id.nodeCreateDefaultRelationShipCheckBox); nodeCreateDefaultRelationShipCheckBox.setChecked(true); dialog.setView(nodeCreateView); dialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { boolean createDefaultRelationship = nodeCreateDefaultRelationShipCheckBox.isChecked(); try { createNode(createDefaultRelationship); } catch (Exception e) { Ln.e(e, "could not create node"); showErrorDialog(); } updateNodesList(); } }); dialog.setButton2(getResources().getString(android.R.string.cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); dialog.show(); } private void createNode(boolean createDefaultRelationship) throws RemoteException, Neo4jServiceException { GraphDatabase database = dbManager.getCurrentNeo4jDatabase(); database.beginTx(); try { ParcelableNode node = new ParcelableNode(); long nodeId = database.createNode(node); Ln.i("created node '" + node + "' with id '" + nodeId + "'"); if (createDefaultRelationship) { ParcelableRelationship relationship = new ParcelableRelationship(); relationship.setStartNodeId(centerNodeId); relationship.setEndNodeId(nodeId); relationship.setName(defaultRelationshipName); long relationshipId = database.createRelationship(relationship); Ln.i("created relationship '" + relationship + "' with id '" + relationshipId + "'"); } database.txSuccess(); } finally { database.txFinish(); } } }