Натан А. Гуд, инженер по программному обеспечению, консультант
Эта статья продемонстрирует, как добавлять новые файлы к существующему проекту Eclipse с использованием мастера. Мастера Eclipse - это мощный инструмент для создания многократно используемых шаблонов применительно к таким типам файлов, которым недостаточно функциональности встроенных шаблонов. После изучения данной статьи вы сможете реализовать собственные мастера Eclipse, предназначенные для создания новых файлов.
Чтобы извлечь максимальную пользу из этой статьи, вы должны быть хорошо знакомы с построением классов на языке программирования Java™, а также с концепциями наследования и с пользовательскими интерфейсами. Вы должны уметь запускать среду Eclipse, однако в статье не предполагается, что вы являетесь высококвалифицированным специалистом в области Eclipse.
Для исполнения приведенных в статье приложений вам потребуется следующее программное обеспечение:
- Eclipse V3.2 или выше
- Хотя вы можете достигнуть определенных результатов и с более ранними версиями, приведенный в статье код тестировался с версией Eclipse V3.2.2 - новейшей официальной версией на момент написания статьи.
- IBM или Sun JDK версии V1.5 или выше
- Упражнения были созданы с использованием среды Java V1.5.0_07 под управлением операционной системы Mac OS® X V10.4.8. Используемая вами операционная система не имеет значения. Гораздо важнее версия Java-среды, установленной на вашем компьютере. Я рекомендую использовать версию Java 5.
Мне нравятся многие возможности интегрированной среды разработки Eclipse. Одна из них - это расширяемость. Эту интегрированную среду разработки можно легко настроить в соответствии с собственными потребностями посредством добавления подключаемых модулей, реализующих нужные функции - в том числе модулей-мастеров, которые автоматизируют создание классов, интерфейсов, проектов и других ресурсов. Это имеет большое значение для организаций, поскольку позволяет превратить разработку и распространение подключаемых модулей на базе Eclipse IDE в оптимизированный процесс, функциональностью которого в автоматическом режиме сможет пользоваться множество людей.
Наличие процесса, позволяющего группам разработки создавать приложения единообразным способом, обеспечивает любой организации множество существенных преимуществ. Единообразие существенно упрощает сопровождение приложений. Единообразие уменьшает количество ошибок. Кроме того, поскольку приложения создаются единообразным способом, участникам группы проще переносить их из одного проекта в другой.
Возможность создания собственных мастеров для платформы Eclipse позволяет предприятию внедрять собственные специализированные мастера, обеспечивающие разработку приложений на основе единых проверенных методик.
Создаваемый в рамках данной статьи мастер представляет собой проект Eclipse для построения подключаемого модуля. Первые этапы построения специализированного мастера существенно упрощаются благодаря другим мастерам, выполняющим первоначальное создание исходного кода вместо разработчика. На протяжении нескольких следующих шагов вы будете использовать мастер Plug-in Project Wizard для создания начальной части своего подключаемого модуля.
Для создания нового проекта Plug-in Project выполните следующие шаги:
- Для выбора мастера проекта последовательно нажмите File > New > Project to select a project wizard.
- В списке Plug-in Development выберите строку Plug-in Project (см. Рис. 1).
- Для перехода к следующему этапу нажмите Next.
Рис. 1. Выбор мастера Plug-in Project
- Введите имя проекта (как можно увидеть на см. Рис. 2, в этой статье используется достаточно незамысловатое имя ExampleWizard). Используйте размещение (Location) по умолчанию, если у вас нет достаточных оснований для его изменения. Нажмите Next.
Рис. 2. Новый проект Plug-in Project
- В строке Plug-in Version введите номер версии своего модуля; в строке Plug-in Name введите имя своего модуля; в строке Plug-in Provider введите имя поставщика своего модуля (например, свое имя или название группы разработки, участником которой вы являетесь). Не забудьте обновить имя пакета в строке Activator, в которую по умолчанию подставляется имя вашего проекта в нижнем регистре. Я рекомендую использовать имя пакета, соответствующее правилам вашей компании, например,
com.example.eclipse.wizards
. Введя всю необходимую информацию (см. Рис. 3), нажмите Next.
Рис. 3. Ввод необходимых данных для проекта Plug-in Project
- Выберите опцию Custom plug-in wizard; это позволит вам осуществить тонкую настройку выбранных вами компонентов. Если до этого вы никогда не создавали новых plug-in-проектов, сейчас самый удобный момент для того, чтобы познакомиться с описаниями других доступных шаблонов. Нажмите Next.
- В окне Template Selection нажмите Deselect All для сброса флажков во всех контрольных окошках. Затем выделите строку New File Wizard, как показано на Рис. 4. Нажмите Next.
Рис. 4. Выбор шаблона
- Мастер Eclipse предлагает ввести определенную информацию о создаваемом новом мастере (см. Рис. 5). Не забудьте обновить имя пакета; идеальным вариантом будет то же имя, которое вы использовали в строке Activator (com.example.eclipse.wizards). Введите в строке Wizard Category Name имя папки для вашего мастера. Это значение используется в качестве категории Plug-in Development (см. Рис. 1). Wizard Class Name - это имя класса Java, которые является потомком класса
Wizard
и реализует интерфейс INewWizard
. В свою очередь, класс Wizard Page Class Name является расширением класса WizardPage
.
Рис. 5. Окно New Wizard Options
- Нажмите Finish. Eclipse добавляет ваш новый проект с необходимыми классами и библиотеками.
Вы еще не довели свою работу до конца, однако уже заложили хорошую основу и готовы приступить к реализация своего мастера.
К данному моменту в вашем проекте есть три класса: NewXHTMLFileWizard
, NewXHTMLFileWizardPage
и Activator
. В следующих разделах рассматривается класс NewXHTMLFileWizard
. Класс показан в листинге 1 без детализации кода методов.
Листинг 1. Класс NewXHTMLFileWizard
public class NewXHTMLFileWizard extends Wizard implements INewWizard {
private NewXHTMLFileWizardPage page;
private ISelection selection;
public NewXHTMLFileWizard() {
// snipped...
}
public void addPages() {
// snipped...
}
public boolean performFinish() {
// snipped...
}
private void doFinish(
// snipped...
}
private InputStream openContentStream() {
// snipped...
}
private void throwCoreException(String message) throws CoreException {
// snipped...
}
public void init(IWorkbench workbench, IStructuredSelection selection) {
// snipped...
}
}
|
Последний метод - init()
- необходим для реализации интерфейса INewWizard
. Далее в статье рассматривается этот и другие методы, которые были автоматически включены в данный шаблон.
Метод addPages()
Метод addPages()
добавляет страницы к вашему мастеру. В листинге 2 этот метод добавляет к мастеру единственную страницу NewXHTMLFileWizardPage
.
Листинг 2. Метод addPages() добавляет страницы к вашему мастеру
/**
* Adding the page to the wizard.
*/
public void addPages() {
page = new NewXHTMLFileWizardPage(selection);
// You can add more pages here...
addPage(page);
}
|
Класс NewXHTMLFileWizardPage
содержит элементы управления, позволяющие пользователю задать имя страницы. Если вы впоследствии захотите предоставить конечному пользователю возможности для ввода другой информации, вы сможете добавить к странице соответствующие средства.
Вызов метода performFinish()
осуществляется в том случае, когда пользователь нажимает кнопку Finish в данном мастере. После выполнения некоторых проверок он вызывает метод doFinish()
с помощью интерфейса IRunnableWithProgress. Использование этого интерфейса избавляет вас от необходимости написания всех элементов пользовательского интерфейса, отвечающих за работу индикатора выполнения метода doFinish()
(в том случае, если его выполнение занимает много времени). Этот метод полностью приведен в следующем листинге.
Листинг 3. Метод performFinish()
/**
* This method is called when 'Finish' button is pressed in
* the wizard. We will create an operation and run it
* using wizard as execution context.
*/
public boolean performFinish() {
final String containerName = page.getContainerName();
final String fileName = page.getFileName();
IRunnableWithProgress op = new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) throws InvocationTargetException {
try {
doFinish(containerName, fileName, monitor);
} catch (CoreException e) {
throw new InvocationTargetException(e);
} finally {
monitor.done();
}
}
};
try {
getContainer().run(true, false, op);
} catch (InterruptedException e) {
return false;
} catch (InvocationTargetException e) {
Throwable realException = e.getTargetException();
MessageDialog.openError(getShell(), "Error", realException.getMessage());
return false;
}
return true;
}
|
Показанный ниже метод doFinish()
создает новый файл и запускает в среде разработки редактор с открытым в нем файлом. Вызов метода openContentStream()
производится для получения потока, из которого новый файл будет заполняться соответствующим контентом.
Листинг 4. Первоначальный вид метода doFinish()
/**
* The worker method. It will find the container, create the
* file if missing or just replace its contents, and open
* the editor on the newly created file.
*/
private void doFinish(
String containerName,
String fileName,
IProgressMonitor monitor)
throws CoreException {
// create a sample file
monitor.beginTask("Creating " + fileName, 2);
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IResource resource = root.findMember(new Path(containerName));
if (!resource.exists() // !(resource instanceof IContainer)) {
throwCoreException("Container \"" + containerName + "\" does not exist.");
}
IContainer container = (IContainer) resource;
final IFile file = container.getFile(new Path(fileName));
try {
InputStream stream = openContentStream();
if (file.exists()) {
file.setContents(stream, true, true, monitor);
} else {
file.create(stream, true, monitor);
}
stream.close();
} catch (IOException e) {
}
monitor.worked(1);
monitor.setTaskName("Opening file for editing...");
getShell().getDisplay().asyncExec(new Runnable() {
public void run() {
IWorkbenchPage page =
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
try {
IDE.openEditor(page, file, true);
} catch (PartInitException e) {
}
}
});
monitor.worked(1);
}
|
Показанный ниже метод openContentStream()
возвращает поток ByteArrayInputStream
, содержащий статическую строку, созданную как составная часть шаблона. В этой статье данная строка заменяется контентом файла-шаблона.
Код данного метода подлежит изменению в первую очередь, если вы захотите разрешить при создании нового файла добавление дополнительного полезного контента.
Листинг 5. Метод openContentStream()
/**
* Initialize file contents with a sample text.
*/
private InputStream openContentStream() {
String contents =
"This is the initial file contents for *.html " +
"file that should be word-sorted in the Preview " +
"page of the multi-page editor";
return new ByteArrayInputStream(contents.getBytes());
}
|
Вместо того, чтобы использовать в качестве контента нового файла значение статической строки, вы можете воспользоваться методом getResourceAsStream()
для загрузки контента файла в код InputStream
, который затем может быть задействован методом doFinish()
для заполнения нового файла. Соответствующие изменения показаны ниже.
Листинг 6. Получение потока из ресурса
/**
* Initialize the file contents to contents of the
* given resource.
*/
private InputStream openContentStream() {
return this.getClass()
.getResourceAsStream("templates/index-xhtml-template.resource");
}
|
В файле index-xhtml-template.resource находится полноценная Web-страница стандарта XHTML V1.0 Strict. Эта страница содержит базовые теги и указатели на таблицу стилей и файлы JavaScript гипотетического предприятия. Этот файл показан в листинге 7. Он находится в том же пакете, что и класс NewXHTMLFileWizard
; соответственно, в этой статье он находится в пакете com.example.eclipse.wizards
. Если вы разместите этот файл в другом пакете, обращайтесь к нему так, как будто это файл в каком-либо каталоге (другими словами, запись com.example.resources
будет означать /com/example/resources
).
Листинг 7. Файл index-xhtml-template.resource
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>This is an Example.com Web page</title>
<link rel="stylesheet" href=
"http://www.example.com/enterprise/styles/main.css" type=
"text/css" />
<script type="text/javascript" language="JavaScript" src=
"http://www.example.com/scripts/main.js">
</script>
</head>
<body>
<div id="search">
<form id="searchForm" name="searchForm" action=
"http://www.example.com/search.jsp">
<input type="text" name="searchText" /> <input type="button"
value="Search Example.com" />
</form>
</div>
<div id="mainMenu">
<p class="menu">Main menu</p>
<ul class="mainMenu">
<li><a href="#home">Home</a></li>
<li><a href="#item1">Item 1</a></li>
</ul>
</div><!-- Put the body of your page here -->
<div id="body"></div>
</body>
</html>
|
Теперь вы уже можете запустить свой подключаемый модуль Eclipse и посмотреть на него.
После того как среда Eclipse создаст три класса, которые используются в этом мастере, вы в любой момент в процессе чтения этой статьи сможете запустить другой экземпляр Eclipse и затем использовать его для прогона и тестирования своего подключаемого модуля. Для запуска своего plug-in-проекта щелкните правой кнопкой мыши на его имени и выберите Run As > Eclipse Application (см. рис. 6). Запустится новый экземпляр Eclipse.
Рис. 6. Прогон проекта в качестве Eclipse-приложения
Теперь вы должны создать временный проект, содержащий ваш новый файл. Имя этого проекта не имеет значения - например, можно назвать его "temp". После появления этого проекта в вашем рабочем пространстве добавьте новый шаблон, для чего выберите File > New > Other.
Если все будет работать так, как ожидалось, в окне Select a Wizard появится новая категория - под той категорией, которую вы ранее сформировали для своего мастера. В этой статье использована категория Example.com Enterprise Templates (см. Рис. 7).
Рис. 7. Использование нового шаблона
После того как вы завершите работу оставшейся части вашего мастера, Eclipse создаст новый файл, в котором будет содержаться заданный вами контент. Если для вашего шаблона достаточно этих простых функций, тогда вы можете на этом закончить. Однако вы можете, например, реализовать такую функцию, как подсказка пользователю о необходимости ввода определенной информации, которую вы затем используете при сборке контента своего файла.
В форме исходной страницы NewXHTMLFileWizardPage
имелись только два элемента управления: один для имени контейнера (проект или папка), другой - для имени создаваемого файла. Метод createControl()
(полностью приведенный в листинге 8) отвечает за создание этих элементов управления и добавление их к диалоговому окну.
Листинг 8. Метод createControl()
/**
* @see IDialogPage#createControl(Composite)
*/
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
GridLayout layout = new GridLayout();
container.setLayout(layout);
layout.numColumns = 3;
layout.verticalSpacing = 9;
Label label = new Label(container, SWT.NULL);
label.setText("&Container:");
containerText = new Text(container, SWT.BORDER / SWT.SINGLE);
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
containerText.setLayoutData(gd);
containerText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
dialogChanged();
}
});
Button button = new Button(container, SWT.PUSH);
button.setText("Browse...");
button.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
handleBrowse();
}
});
label = new Label(container, SWT.NULL);
label.setText("&File name:");
fileText = new Text(container, SWT.BORDER / SWT.SINGLE);
gd = new GridData(GridData.FILL_HORIZONTAL);
fileText.setLayoutData(gd);
fileText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
dialogChanged();
}
});
initialize();
dialogChanged();
setControl(container);
}
|
Прежде чем вы сможете добавить к этому методу новый элемент управления, необходимо объявить его в начале файла вместе с другими элементами управления.
Листинг 9. Объявление нового элемента управления
public class NewXHTMLFileWizardPage extends WizardPage {
/* Newly added for the page title */
private Text titleText;
// the rest of the class...
}
|
Теперь добавьте к этому тексту т.н. getter-метод. Класс NewXHTMLFileWizard
будет использовать этот getter-метод при построения нового файла. Метод getTitle()
показан в следующем листинге.
Листинг 10. Метод getTitle()
/**
* Gets the HTML title for the new file
*/
public String getTitle() {
return titleText.getText();
}
|
Внесите в метод createControl()
необходимые изменения, добавляющие новые элементы управления вводом и метку для заголовка. Измененный код показан в следующем листинге.
Листинг 11. Метод createControl() с внесенными изменениями
/**
* @see IDialogPage#createControl(Composite)
*/
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
GridLayout layout = new GridLayout();
container.setLayout(layout);
layout.numColumns = 3;
layout.verticalSpacing = 9;
Label label = new Label(container, SWT.NULL);
label.setText("&Container:");
containerText = new Text(container, SWT.BORDER / SWT.SINGLE);
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
containerText.setLayoutData(gd);
containerText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
dialogChanged();
}
});
Button button = new Button(container, SWT.PUSH);
button.setText("Browse...");
button.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
handleBrowse();
}
});
label = new Label(container, SWT.NULL);
label.setText("&File name:");
fileText = new Text(container, SWT.BORDER / SWT.SINGLE);
gd = new GridData(GridData.FILL_HORIZONTAL);
fileText.setLayoutData(gd);
fileText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
dialogChanged();
}
});
/* Need to add empty label so the next two controls
* are pushed to the next line in the grid. */
label = new Label(container, SWT.NULL);
label.setText("");
/* Adding the custom control here */
label = new Label(container, SWT.NULL);
label.setText("&Title:");
titleText = new Text(container, SWT.BORDER / SWT.SINGLE);
gd = new GridData(GridData.FILL_HORIZONTAL);
titleText.setLayoutData(gd);
titleText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
dialogChanged();
}
});
/* Finished adding the custom control */
initialize();
dialogChanged();
setControl(container);
}
|
Прежде чем добавлять код к классу NewXHTMLFileWizard
, протестируйте изменения, внесенные к настоящему времени, для чего выполните описанные выше шаги.
Текст, которые пользователь вводит в поле Title нового мастера, будет использоваться в качестве текста в элементе <title>
нового HTML-файла. В рамках данной статьи это значение является обязательным, поэтому вы должны добавить соответствующий код, которые будет проверять, ввел ли пользователь какую-либо информацию.
В следующем листинге показаны изменения, внесенные в метод dialogChanged()
.
Листинг 12. Проверка наличия введенной информации в методе dialogChanged()
/**
* Ensures that both text fields are set.
*/
private void dialogChanged() {
IResource container = ResourcesPlugin.getWorkspace().getRoot()
.findMember(new Path(getContainerName()));
String fileName = getFileName();
String titleText = getTitle();
if (getContainerName().length() == 0) {
updateStatus("File container must be specified");
return;
}
if (container == null
// (container.getType() & (IResource.PROJECT / IResource.FOLDER)) == 0) {
updateStatus("File container must exist");
return;
}
if (!container.isAccessible()) {
updateStatus("Project must be writable");
return;
}
if (fileName.length() == 0) {
updateStatus("File name must be specified");
return;
}
if (fileName.replace('\\', '/').indexOf('/', 1) > 0) {
updateStatus("File name must be valid");
return;
}
int dotLoc = fileName.lastIndexOf('.');
if (dotLoc != -1) {
String ext = fileName.substring(dotLoc + 1);
if (ext.equalsIgnoreCase("html") == false) {
updateStatus("File extension must be \"html\"");
return;
}
}
if (titleText.length() ==0 )
{
updateStatus("Title must be specified");
return;
}
updateStatus(null);
}
|
После того, как указанные изменения будут внесены, мастер будет генерировать сообщение об ошибке в том случае, если пользователь не введет значение в поле Title. Кроме того, кнопка Finish будет деактивирована до тех пор, пока пользователь не введет в поле Title какое-либо значение. Убедитесь в работоспособности этой функции, для чего выполните описанные выше шаги.
Теперь страница мастера NewXHTMLFileWizardPage
уже может принимать введенное пользователем значение для HTML-заголовка, однако пока не способна внедрять это значение в файл. Перед тем как начинать добавление значения к файлу, сначала отредактируйте файл index-xhtml-template.resource, создав в нем т.н. «заполнитель» (placeholder) для этого значения. Для упрощения подстановки значения можно заменить элемент <title>
на <title>${title}</title>
.
Напоминаю, что вы модифицируете метод performFinish()
для того, чтобы получить заголовок из страницы мастера и передать его в метод doFinish()
вместе с остальными значениями.
Листинг 13. Окончательный вид метода performFinish()
/**
* This method is called when 'Finish' button is pressed in the wizard. We
* will create an operation and run it using wizard as execution context.
*/
public boolean performFinish() {
final String containerName = page.getContainerName();
final String fileName = page.getFileName();
final String title = page.getTitle();
IRunnableWithProgress op = new IRunnableWithProgress() {
public void run(IProgressMonitor monitor)
throws InvocationTargetException {
try {
doFinish(containerName, fileName, title, monitor);
} catch (CoreException e) {
throw new InvocationTargetException(e);
} finally {
monitor.done();
}
}
};
try {
getContainer().run(true, false, op);
} catch (InterruptedException e) {
return false;
} catch (InvocationTargetException e) {
Throwable realException = e.getTargetException();
MessageDialog.openError(getShell(), "Error", realException
.getMessage());
return false;
}
return true;
}
|
Теперь внесите в метод doFinish()
небольшие изменения, позволяющие принять заголовок в качестве параметра и передать его в метод openContentStream()
.
Листинг 14. Окончательный вид метода doFinish(), принимающего заголовок в качестве параметра
/**
* The worker method. It will find the container, create the file if missing
* or just replace its contents, and open the editor on the newly created
* file.
*/
private void doFinish(String containerName, String fileName, String title,
IProgressMonitor monitor) throws CoreException {
monitor.beginTask("Creating " + fileName, 2);
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IResource resource = root.findMember(new Path(containerName));
if (!resource.exists() // !(resource instanceof IContainer)) {
throwCoreException("Container \"" + containerName
+ "\" does not exist.");
}
IContainer container = (IContainer) resource;
final IFile file = container.getFile(new Path(fileName));
try {
InputStream stream = openContentStream(title);
try {
if (file.exists()) {
file.setContents(stream, true, true, monitor);
} else {
file.create(stream, true, monitor);
}
} finally {
stream.close();
}
} catch (IOException e) {
}
monitor.worked(1);
monitor.setTaskName("Opening file for editing...");
getShell().getDisplay().asyncExec(new Runnable() {
public void run() {
IWorkbenchPage page = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getActivePage();
try {
IDE.openEditor(page, file, true);
} catch (PartInitException e) {
}
}
});
monitor.worked(1);
}
|
И, наконец, вам необходимо немного модифицировать метод openContentStream()
, чтобы он смог заменять значение $title
, найденное в файле, значением, введенным пользователем (см. листинг 15). Если шаблон содержит большое количество различных значений, вы можете применить более элегантное решение, например, новый класс, который расширяет FilterInputStream
и заменяет сразу весь набор различных значений.
Листинг 15. Окончательный вид метода openContentStream()
/**
* Initialize the file contents to contents of the given resource.
*/
private InputStream openContentStream(String title
)
throws CoreException {
final String newline = "\n"; // System.getProperty("line.separator");
String line;
StringBuffer sb = new StringBuffer();
try {
InputStream input = this.getClass().getResourceAsStream(
"index-xhtml-template.resource");
BufferedReader reader = new BufferedReader(new InputStreamReader(
input));
try {
while ((line = reader.readLine()) != null) {
line = line.replaceAll("\\$\\{title\\}", title);
sb.append(line);
sb.append(newline);
}
} finally {
reader.close();
}
} catch (IOException ioe) {
IStatus status = new Status(IStatus.ERROR, "ExampleWizard", IStatus.OK,
ioe.getLocalizedMessage(), null);
throw new CoreException(status);
}
return new ByteArrayInputStream(sb.toString().getBytes());
}
|
Теперь метод openContentStream()
не ограничивается загрузкой контента файла ресурсов и возвращением его в качестве потока InputStream
. Новый код периодически обращается к потоку, читает его с помощью InputStreamReader
и в каждой строке заменяет значение $title
. Результат возвращается в виде потока ByteArrayInputStream
- это тот же самый поток, который использовался при первоначальном создании класса NewXHTMLFileWizard
.
|
Обращение к потоку
Когда в первом написанном мной коде я нажал в мастере на кнопку Finish, в ответ была получена ошибка Invalid thread access . Мой код обращался к page.getTitle() непосредственно из метода openContentStream() , а это не является корректным. Значение должно быть определено классом NewXHTMLFileWizard и передано в объект op метода performFinish() . | |
Если вы до сих пор следовали моим указаниям, то к настоящему моменту у вас имеется мастер, который создает новый файл в существующем проекте. Однако не будем ограничиваться этим. Современное предприятие может представить свои корпоративные правила, которые оно считает обязательными для соблюдения, в виде таких ресурсов, как XHTML-файлы. Вполне вероятно, что это предприятие захочет иметь аналогичные соглашения по организации новых проектов.
Посредством сравнительно небольших изменений в вашем проекте вы сможете построить мастера, которые будет добавлять весь проект к рабочему пространству вместе с необходимыми папками и некоторыми первоначальными файлами. Этот мастер создает новую папку для Web-сайта Example.com, а также образы и папки стилей. Внутри папки стилей этот мастер создает CSS-файл с названием site.css. Мастер функционирует посредством повторного вызова метода из класса NewXHTMLFileWizard
для добавления нового XHTML-файла с заголовком, содержащим имя нового проекта, и некоторого нового текста.
Поскольку к данному моменту вы уже имеете настроенный и работоспособный подключаемый модуль, у вас нет необходимости в использовании мастера для построения нового класса. Вместо этого вы можете построить новый мастер самостоятельно - создав расширением класса Wizard новый класс, который будет реализовывать следующие два интерфейса: INewWizard
и IExecutableExtension
.
Добавьте новый класс NewSiteProjectWizard
в тот же пакет, в котором находится класс NewXHTMLFileWizard
. Посмотрите на декларацию класса NewSiteProjectWizard
в листинге 16 и убедитесь в том, что вы расширяете класс Wizard
. Кроме того, добавьте интерфейсы INewWizard
и IExecutableExtension
.
Поскольку класс NewSiteProjectWizard
является расширением того же класса NewXHTMLFileWizard
и реализует один из интерфейсов, реализованных в классе NewXHTMLFileWizard
, вы увидите в этих двух классах общие методы. Для компактности контент методов в листинге 16 показан в сокращенном виде (далее в статье эти методы будут продемонстрированы в полном виде).
Листинг 16. Класс NewSiteProjectWizard
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExecutableExtension;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.dialogs.WizardNewProjectCreationPage;
import org.eclipse.ui.wizards.newresource.BasicNewProjectResourceWizard;
public class NewSiteProjectWizard extends Wizard implements INewWizard,
IExecutableExtension {
/*
* Use the WizardNewProjectCreationPage, which is provided by the Eclipse
* framework.
*/
private WizardNewProjectCreationPage wizardPage;
private IConfigurationElement config;
private IWorkbench workbench;
private IStructuredSelection selection;
private IProject project;
/**
* Constructor
*/
public NewSiteProjectWizard() {
super();
}
public void addPages() {
// snipped...
}
@Override
public boolean performFinish() {
// snipped...
}
/**
* This creates the project in the workspace.
*
* @param description
* @param projectHandle
* @param monitor
* @throws CoreException
* @throws OperationCanceledException
*/
void createProject(IProjectDescription description, IProject proj,
IProgressMonitor monitor) throws CoreException,
OperationCanceledException {
// snipped...
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.IWorkbenchWizard#init(org.eclipse.ui.IWorkbench,
* org.eclipse.jface.viewers.IStructuredSelection)
*/
public void init(IWorkbench workbench, IStructuredSelection selection) {
// snipped...
}
/**
* Sets the initialization data for the wizard.
*/
public void setInitializationData(IConfigurationElement config,
String propertyName, Object data) throws CoreException {
// snipped...
}
/**
* Adds a new file to the project.
*
* @param container
* @param path
* @param contentStream
* @param monitor
* @throws CoreException
*/
private void addFileToProject(IContainer container, Path path,
InputStream contentStream, IProgressMonitor monitor)
throws CoreException {
// snipped
}
} |
После того, как вы добавили новый класс, и перед тем, как вы сможете исполнить его в среде Eclipse в качестве мастера, вам необходимо внести некоторые изменения в файл plugin.xml file , расположенный в базовой части вашего проекта.
Листинг 17. Файл plugin.xml
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
<extension
point="org.eclipse.ui.newWizards">
<category
name="Example.com Enterprise Templates"
id="ExampleWizard">
</category>
<wizard
name="Example.com Static Web Page"
icon="icons/sample.gif"
category="ExampleWizard"
class="com.example.eclipse.wizards.NewXHTMLFileWizard"
id="com.example.eclipse.wizards.NewXHTMLFileWizard">
</wizard>
<wizard
category="ExampleWizard"
class="com.example.eclipse.wizards.NewSiteProjectWizard"
icon="icons/sample.gif"
id="com.example.eclipse.wizards.NewSiteProjectWizard"
name="Example.com Static Web Site"
project="true">
</wizard>
</extension>
</plugin>
|
Изменения, внесенные в файл plugin.xml, позволят среде Eclipse узнать, что класс NewSiteProjectWizard
является мастером, который она сможет при необходимости вызвать. Этот класс находится под той же категорией, что и описывавшийся выше класс NewXHTMLFileWizard
. Атрибут project="true"
сообщает среде Eclipse, что речь идет о проекте, в результате чего этот проект будет отображаться в соответствующем контексте.
API-интерфейсы среды Eclipse содержат новые классы мастеров и страниц мастеров. Эти классы окажутся полезными, если вы хотите реализовать базовые функции и не нуждаетесь в реализации большого количества пользовательских функций. В техническом смысле класс NewSiteProjectWizard
может являться расширением класса BasicNewProjectResourceWizard
- существующего мастера, который используется для создания базовых проектов - хотя в документе JavaDoc отмечается, что этот класс не предназначался для использования в качестве подкласса. Как показано ниже, для получения базовой информации по проекту, например, имени проекта, вы можете использовать ту же страницу, которая использовалась мастером BasicNewProjectResourceWizard
, а именно WizardNewProjectCreationPage
.
Листинг 18. Метод addPages()
public void addPages() {
/*
* Unlike the custom new wizard, we just add the pre-defined one and
* don't necessarily define our own.
*/
wizardPage = new WizardNewProjectCreationPage(
"NewExampleComSiteProject");
wizardPage.setDescription("Create a new Example.com Site Project.");
wizardPage.setTitle("New Example.com Site Project");
addPage(wizardPage);
}
|
Этот метод создает новый экземпляр класса страницы, вводит описание и заголовок, а затем добавляет все это в качестве страницы мастера.
Как и класс NewXHTMLFileWizard
, класс NewSiteProjectWizard
содержит показанный в листинге 19 метод performFinish()
, который выполняется после того, как пользователь выполнил все шаги в мастере и нажал на кнопку Finish. Этот метод вызывает процесс, выполняющий метод createProject()
, который, в свою очередь, выполняет большую часть основной работы, включая создание проекта, папки и файлов.
Листинг 19. Метод performFinish()
@Override
public boolean performFinish() {
if (project != null) {
return true;
}
final IProject projectHandle = wizardPage.getProjectHandle();
URI projectURI = (!wizardPage.useDefaults()) ? wizardPage
.getLocationURI() : null;
IWorkspace workspace = ResourcesPlugin.getWorkspace();
final IProjectDescription desc = workspace
.newProjectDescription(projectHandle.getName());
desc.setLocationURI(projectURI);
/*
* Just like the ExampleWizard, but this time with an operation object
* that modifies workspaces.
*/
WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
protected void execute(IProgressMonitor monitor)
throws CoreException {
createProject(desc, projectHandle, monitor);
}
};
/*
* This isn't as robust as the code in the BasicNewProjectResourceWizard
* class. Consider beefing this up to improve error handling.
*/
try {
getContainer().run(true, true, op);
} catch (InterruptedException e) {
return false;
} catch (InvocationTargetException e) {
Throwable realException = e.getTargetException();
MessageDialog.openError(getShell(), "Error", realException
.getMessage());
return false;
}
project = projectHandle;
if (project == null) {
return false;
}
BasicNewProjectResourceWizard.updatePerspective(config);
BasicNewProjectResourceWizard.selectAndReveal(project, workbench
.getActiveWorkbenchWindow());
return true;
}
|
После вызова метода createProject()
для создания файлов и папок метод performFinish()
вызывает два статических метода для обновления текущей перспективы и для выбора вновь созданного проекта в среде разработки.
Метод createProject()
Метод createProject()
, показанный в листинге 20, создает, а затем открывает новый проект. Затем этот метод добавляет к указанному проекту два новых файла и две новые папки. Эти файлы добавляются private-методом addFileToProject()
, который был написан для того, чтобы до некоторой степени сохранить «чистоту» метода createProject()
.
Листинг 20. Метод createProject()
/**
* This creates the project in the workspace.
*
* @param description
* @param projectHandle
* @param monitor
* @throws CoreException
* @throws OperationCanceledException
*/
void createProject(IProjectDescription description, IProject proj,
IProgressMonitor monitor) throws CoreException,
OperationCanceledException {
try {
monitor.beginTask("", 2000);
proj.create(description, new SubProgressMonitor(monitor, 1000));
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
proj.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(
monitor, 1000));
/*
* Okay, now we have the project and we can do more things with it
* before updating the perspective.
*/
IContainer container = (IContainer) proj;
/* Add an XHTML file */
addFileToProject(container, new Path("index.html"),
NewXHTMLFileWizard.openContentStream("Welcome to "
+ proj.getName()), monitor);
/* Add the style folder and the site.css file to it */
final IFolder styleFolder = container.getFolder(new Path("styles"));
styleFolder.create(true, true, monitor);
InputStream resourceStream = this.getClass().getResourceAsStream(
"templates/site-css-template.resource");
addFileToProject(container, new Path(styleFolder.getName()
+ Path.SEPARATOR + "style.css"),
resourceStream, monitor);
resourceStream.close();
/*
* Add the images folder, which is an official Exmample.com standard
* for static web projects.
*/
IFolder imageFolder = container.getFolder(new Path("images"));
imageFolder.create(true, true, monitor);
} catch (IOException ioe) {
IStatus status = new Status(IStatus.ERROR, "ExampleWizard", IStatus.OK,
ioe.getLocalizedMessage(), null);
throw new CoreException(status);
} finally {
monitor.done();
}
}
|
Метод addFileToProject()
Возможно, вы обратили внимание, что большая часть кода в методе addFileToProject()
по существу совпадает с кодом метода doFinish()
, показанного в листинге 14. Однако сигнатура этого метода совершенно другая. Внесенные изменения позволяют принимать параметры, что расширяет возможности повторного использования в контексте метода createProject()
.
Листинг 21. Метод addFileToProject()
/**
* Adds a new file to the project.
*
* @param container
* @param path
* @param contentStream
* @param monitor
* @throws CoreException
*/
private void addFileToProject(IContainer container, Path path,
InputStream contentStream, IProgressMonitor monitor)
throws CoreException {
final IFile file = container.getFile(path);
if (file.exists()) {
file.setContents(contentStream, true, true, monitor);
} else {
file.create(contentStream, true, monitor);
}
}
|
Если этот файл уже существует, его контент соответствует контенту потока contentStream
, переданного в этот метод. Если этот файл не существует, он будет создан вместе с находящимся внутри него контентом.
В материалы для загрузки по данной статье включен полный текст класса NewSiteProjectWizard
. После того как вы добавите показанные здесь реализации метода и реализацию интерфейсов INewWizard
и IExecutableExtension
, вы сможете выполнять свои проекты в качестве Eclipse-приложений, как было показано выше. На этот раз помимо создания нового файла с помощью мастера NewXHTMLFileWizard
, вы сможете создать и новый проект.
Если в процессе прогона вы получили сообщения об ошибке, вы можете в процессе отладки прогнать свой проект как подключаемый модуль Eclipse. В таких случаях я предпочитаю использовать перспективу Debug Perspective среды разработки Eclipse. Чтобы начать прогон подключаемого модуля в среде Eclipse одновременно с пошаговым выполнением вашего кода, выберите Run > Debug Last Launched.
Имеет смысл ввести контрольную точку в первую строку метода performFinish()
, поскольку именно здесь начинаются реальные действия. После того, как вы в своем мастере нажмете кнопку Finish, отладчик должен будет прервать свою работу в этой контрольной точке (при условии, что ошибка не возникла до этой контрольной точки).
Одно из величайших достоинств среды разработки Eclipse - это простота расширения функциональности посредством создания подключаемых модулей, добавляющих новые мастера для создания новых файлов. Использование специфических для конкретного предприятия мастеров для создания таких файлов обеспечивает единообразную и быструю разработку приложений.